v0.1.3: encoding remediation - restore 11 core files to clean UTF-8 + add check:encoding-health guard
This commit is contained in:
@@ -8,7 +8,7 @@ the corpus internally consistent and publish-ready.
|
||||
The corpus does not assume any specific forge (GitHub / Gitea / GitLab),
|
||||
ticketing, or CI system. System-specific behavior lives behind an explicit
|
||||
adapter contract (`core/adapters/`), so the same core can be adopted by any
|
||||
octopus instance — or any other agent platform — with its own adapters.
|
||||
octopus instance 鈥?or any other agent platform 鈥?with its own adapters.
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -20,7 +20,7 @@ octopus instance — or any other agent platform — with its own adapters.
|
||||
| `core/templates/` | Artifact templates (DAG, iteration docs) |
|
||||
| `core/schemas/` | JSON schemas under the public namespace `https://eightarms.net/octopus-workflow/schemas/` |
|
||||
| `core/adapters/` | Adapter contract: terminology SSOT + Gitea reference implementation |
|
||||
| `core/CORE-MANIFEST.json` | Registry of core↔instance mappings and sync semantics (verbatim / verbatimDir / rewritten / split / core-only) |
|
||||
| `core/CORE-MANIFEST.json` | Registry of core鈫攊nstance mappings and sync semantics (verbatim / verbatimDir / rewritten / split / core-only) |
|
||||
| `examples/` | Organization-agnostic narrative essays referencing the core |
|
||||
| `guards/` | Standalone drift guards (bun + node stdlib only) |
|
||||
|
||||
@@ -32,6 +32,7 @@ Runnable inside this repo with [bun](https://bun.sh), no install step needed:
|
||||
bun run check:core-cohesion # corpus structural invariants
|
||||
bun run check:core-p1 # publish-readiness neutrality (no org-internal links)
|
||||
bun run delink:core -- --check # no replaceable org-internal refs remain
|
||||
bun run check:encoding-health # core/ text is clean UTF-8 (no GBK mojibake)
|
||||
```
|
||||
|
||||
The instance-side guards (`check-core-parity`, `check-schema-ids`,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://eightarms.net/octopus-workflow/schemas/port-analysis.schema.json",
|
||||
"title": "Port Analysis Reviewer Output",
|
||||
"description": "Schema for per-dimension port-analysis reviewer findings. Used by the large-port-pipeline source analysis review (Phase B1.5). The port source-analysis pipeline uses a deliberate three-value severity contract 鈥?BLOCKER / MAJOR / MINOR 鈥?and never emits INFO (matching implement/reference/large-port-pipeline.md and source-analysis.md, which name only those three). This is a documented exception to the canonical four-value set (BLOCKER / MAJOR / MINOR / INFO) in reviewer-output.schema.json and synthesis.schema.json.",
|
||||
"description": "Schema for per-dimension port-analysis reviewer findings. Used by the large-port-pipeline source analysis review (Phase B1.5). The port source-analysis pipeline uses a deliberate three-value severity contract — BLOCKER / MAJOR / MINOR — and never emits INFO (matching implement/reference/large-port-pipeline.md and source-analysis.md, which name only those three). This is a documented exception to the canonical four-value set (BLOCKER / MAJOR / MINOR / INFO) in reviewer-output.schema.json and synthesis.schema.json.",
|
||||
"type": "object",
|
||||
"required": ["dimension", "findings"],
|
||||
"properties": {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"max_rounds": {
|
||||
"type": "integer",
|
||||
"default": 3,
|
||||
"description": "Per-skill bound: `review-artifact` skill (process target) = 3 (the roadmap/design/plan targets were archived 2026-08-21, [org-internal #3072] phase 3); `review-code` = 2 default / 3 high-risk (`workflow-routing.yaml` `review.max_rounds`, [org-internal #3567]). `review-dag` (the review-artifact skill's DAG single-gate target) binds per depth from `dag_metrics.review_depth`: D1 鈮?, D2 鈮?, D3 鈮?, D4 鈮? 鈥?D4 reaches round 4, exceeding the shared `review-artifact` default, so the DAG per-depth cap takes precedence (see review-artifact/SKILL.md review-dag Phase D MAX_ROUNDS). Set explicitly by the skill; default 3 is a fallback only."
|
||||
"description": "Per-skill bound: `review-artifact` skill (process target) = 3 (the roadmap/design/plan targets were archived 2026-08-21, [org-internal #3072] phase 3); `review-code` = 2 default / 3 high-risk (`workflow-routing.yaml` `review.max_rounds`, [org-internal #3567]). `review-dag` (the review-artifact skill's DAG single-gate target) binds per depth from `dag_metrics.review_depth`: D1 ≤2, D2 ≤3, D3 ≤3, D4 ≤4 — D4 reaches round 4, exceeding the shared `review-artifact` default, so the DAG per-depth cap takes precedence (see review-artifact/SKILL.md review-dag Phase D MAX_ROUNDS). Set explicitly by the skill; default 3 is a fallback only."
|
||||
},
|
||||
"converged": {
|
||||
"type": "boolean"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"reviewer_slot": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "1-based slot of this reviewer WITHIN its dimension, when dim_split assigns >1 reviewer to the dimension (e.g. review-dag D3 TOPO:2, D4 TOPO:2 鈥?dim_split per `workflow-routing.yaml` dag.size_derivation.review_depth). Multi-reviewer dimensions write one file per slot: findings-{DIMENSION_CODE}-r{k}.json. Omitted for single-reviewer dimensions (file stays findings-{DIMENSION_CODE}.json). Preserves per-reviewer attribution for the retrospective overlap analysis ([org-internal #2996] step 1a)."
|
||||
"description": "1-based slot of this reviewer WITHIN its dimension, when dim_split assigns >1 reviewer to the dimension (e.g. review-dag D3 TOPO:2, D4 TOPO:2 — dim_split per `workflow-routing.yaml` dag.size_derivation.review_depth). Multi-reviewer dimensions write one file per slot: findings-{DIMENSION_CODE}-r{k}.json. Omitted for single-reviewer dimensions (file stays findings-{DIMENSION_CODE}.json). Preserves per-reviewer attribution for the retrospective overlap analysis ([org-internal #2996] step 1a)."
|
||||
},
|
||||
"reviewer_count": {
|
||||
"type": "integer",
|
||||
@@ -79,7 +79,7 @@
|
||||
"checklist_ref": {
|
||||
"type": "string",
|
||||
"pattern": "^[A-Z0-9]{2,6} \\d+(\\.\\d+)+(\\s*\\([^\\)]*\\))?(\\s*/\\s*[A-Z0-9]{2,6} \\d+(\\.\\d+)+(\\s*\\([^\\)]*\\))?)*$",
|
||||
"description": "Checklist item reference, e.g. COR 1.1, INV 1.3, SEC 3.7.1, STD 8.4 鈥?dimension/section code followed by item number(s)."
|
||||
"description": "Checklist item reference, e.g. COR 1.1, INV 1.3, SEC 3.7.1, STD 8.4 — dimension/section code followed by item number(s)."
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"index": true,
|
||||
"working_notes": {
|
||||
"type": "string",
|
||||
"description": "Final content of runs/{slug}/working-notes.md ([org-internal #2600]), inlined at archive time when the file exists. The one Tier 1 artifact whose CONTENT (not just digest) rides in the bundle 鈥?the run's narrative memory (decisions, dead ends, reviewer feedback), consumed by later retro probes and the notes-injection degradation path ([org-internal #2601]). Absent when the run kept no working notes."
|
||||
"description": "Final content of runs/{slug}/working-notes.md ([org-internal #2600]), inlined at archive time when the file exists. The one Tier 1 artifact whose CONTENT (not just digest) rides in the bundle — the run's narrative memory (decisions, dead ends, reviewer feedback), consumed by later retro probes and the notes-injection degradation path ([org-internal #2601]). Absent when the run kept no working notes."
|
||||
},
|
||||
"review_history": {
|
||||
"type": "array",
|
||||
@@ -55,7 +55,7 @@
|
||||
"stage": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "Review stage identifier matching the reviews/{stage}/ directory (e.g. code, review-dag, audit-process 鈥?the live values; design-space is a retired historical value)."
|
||||
"description": "Review stage identifier matching the reviews/{stage}/ directory (e.g. code, review-dag, audit-process — the live values; design-space is a retired historical value)."
|
||||
},
|
||||
"rounds": { "type": "integer", "minimum": 1, "description": "Number of executed rounds." },
|
||||
"final_verdict": { "type": "string", "enum": ["PASS", "WARN", "FAIL"] },
|
||||
@@ -109,7 +109,7 @@
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": ["slug", "state", "created_at"],
|
||||
"description": "Legacy profile (schema_version 1 or absent): historical bundles AND current live-workspace runs 鈥?runs-meta.schema.json / runs-index.schema.json pin schema_version 1 today. Core fields type-checked when present; extra context properties (route/pr/node/kind/...) tolerated as-is. The v2 strict profile (below) is the migration target: it becomes the profile for new runs once the live-workspace schemas are bumped to emit schema_version 2 with the context bag.",
|
||||
"description": "Legacy profile (schema_version 1 or absent): historical bundles AND current live-workspace runs — runs-meta.schema.json / runs-index.schema.json pin schema_version 1 today. Core fields type-checked when present; extra context properties (route/pr/node/kind/...) tolerated as-is. The v2 strict profile (below) is the migration target: it becomes the profile for new runs once the live-workspace schemas are bumped to emit schema_version 2 with the context bag.",
|
||||
"properties": {
|
||||
"schema_version": { "type": "integer", "minimum": 1 },
|
||||
"slug": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
|
||||
@@ -132,7 +132,7 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schema_version", "slug", "state", "created_at", "updated_at", "ticket"],
|
||||
"description": "Strict profile (schema_version 2): closed core field set. Run-specific context (route/pr/node/task_node/...) goes in the context bag 鈥?the ONLY place for non-core fields.",
|
||||
"description": "Strict profile (schema_version 2): closed core field set. Run-specific context (route/pr/node/task_node/...) goes in the context bag — the ONLY place for non-core fields.",
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "state": { "const": "archived" } }, "required": ["state"] },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://eightarms.net/octopus-workflow/schemas/runs-index.schema.json",
|
||||
"title": "Tier 1 Run Artifact Index",
|
||||
"description": "Schema for runs/{slug}/index.json 鈥?the artifact manifest for a Tier 1 local structured run. This is the compact-recovery entry point after compaction (compact never deletes local files). Parallel to, not a replacement for, the Tier 2 '## 宸ヤ欢绱㈠紩' issue comment. Authoritative layout: templates/runs-layout.md. Boundary rule: rules/two-tier-artifacts.md.",
|
||||
"description": "Schema for runs/{slug}/index.json — the artifact manifest for a Tier 1 local structured run. This is the compact-recovery entry point after compaction (compact never deletes local files). Parallel to, not a replacement for, the Tier 2 '## 工件索引' issue comment. Authoritative layout: templates/runs-layout.md. Boundary rule: rules/two-tier-artifacts.md.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["schema_version", "slug", "artifacts"],
|
||||
@@ -24,7 +24,7 @@
|
||||
"artifacts": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/artifact" },
|
||||
"description": "One entry per Tier 1 artifact in this run. Tier 2 artifacts (Gitea issue comments, wiki pages, commit status, PRs) are NEVER listed here 鈥?this index is Tier 1 only."
|
||||
"description": "One entry per Tier 1 artifact in this run. Tier 2 artifacts (Gitea issue comments, wiki pages, commit status, PRs) are NEVER listed here — this index is Tier 1 only."
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
@@ -55,7 +55,7 @@
|
||||
"browser-evidence",
|
||||
"other"
|
||||
],
|
||||
"description": "Artifact category. 'meta'/'index' for the manifest files themselves; stage categories for produced artifacts. 'review-synthesis' marks a synthesis result artifact (round verdict) as distinct from 'synthesis-prompt' (the Phase B task prompt) and 'review-status' (the lifecycle file). 'precondition-gate' marks the review-code mechanical-green gate log. 'working-notes' is the cross-stage narrative memory file ([org-internal #2600]) 鈥?one file per run, appended by every role at close, registered once (stage/produced_by null: multi-stage, multi-role). 'browser-evidence' marks a browser evidence pack manifest ([org-internal #4497] N-03, browser/{session-id}/manifest.json 鈥?shared/pack-manifest-v1)."
|
||||
"description": "Artifact category. 'meta'/'index' for the manifest files themselves; stage categories for produced artifacts. 'review-synthesis' marks a synthesis result artifact (round verdict) as distinct from 'synthesis-prompt' (the Phase B task prompt) and 'review-status' (the lifecycle file). 'precondition-gate' marks the review-code mechanical-green gate log. 'working-notes' is the cross-stage narrative memory file ([org-internal #2600]) — one file per run, appended by every role at close, registered once (stage/produced_by null: multi-stage, multi-role). 'browser-evidence' marks a browser evidence pack manifest ([org-internal #4497] N-03, browser/{session-id}/manifest.json — shared/pack-manifest-v1)."
|
||||
},
|
||||
"schema": {
|
||||
"type": ["string", "null"],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://eightarms.net/octopus-workflow/schemas/runs-meta.schema.json",
|
||||
"title": "Tier 1 Run Metadata",
|
||||
"description": "Schema for runs/{slug}/meta.json 鈥?run metadata for a Tier 1 local structured run. Authoritative layout: templates/runs-layout.md. Boundary rule: rules/two-tier-artifacts.md. State transitions active -> archived at close (archive-at-close).",
|
||||
"description": "Schema for runs/{slug}/meta.json — run metadata for a Tier 1 local structured run. Authoritative layout: templates/runs-layout.md. Boundary rule: rules/two-tier-artifacts.md. State transitions active -> archived at close (archive-at-close).",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
@@ -89,12 +89,12 @@
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "RFC 3339 timestamp 鈥?run creation."
|
||||
"description": "RFC 3339 timestamp — run creation."
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "RFC 3339 timestamp 鈥?last metadata mutation."
|
||||
"description": "RFC 3339 timestamp — last metadata mutation."
|
||||
},
|
||||
"closed_at": {
|
||||
"type": ["string", "null"],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://eightarms.net/octopus-workflow/schemas/workflow-routing.schema.json",
|
||||
"title": "Workflow Routing Table",
|
||||
"description": "Schema for workflow-routing.yaml 鈥?type-driven (Kind/*) process selection. Referenced by rules/workflow-routing.md and the auto-approve rule. When this schema and the rules markdown disagree, the YAML (validated by this schema) is authoritative.",
|
||||
"description": "Schema for workflow-routing.yaml — type-driven (Kind/*) process selection. Referenced by rules/workflow-routing.md and the auto-approve rule. When this schema and the rules markdown disagree, the YAML (validated by this schema) is authoritative.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["version", "default", "routes"],
|
||||
@@ -23,7 +23,7 @@
|
||||
"$defs": {
|
||||
"stage": {
|
||||
"type": "string",
|
||||
"description": "A pipeline stage identifier 鈥?the 5 live gates (post-[org-internal #3072] phase 3, 2026-08-21): review-dag, review-code, verify, audit-process, merge-pr. Production-phase names (requirements, design, roadmap, implement) and the 7 retired stage ids (roadmap-exit, review-roadmap, requirements-exit, design-exit, review-design-space, review-iteration-plan, plan-iterations) are no longer valid anywhere 鈥?the sizing block that consumed the phase names was retired the same ruling.",
|
||||
"description": "A pipeline stage identifier — the 5 live gates (post-[org-internal #3072] phase 3, 2026-08-21): review-dag, review-code, verify, audit-process, merge-pr. Production-phase names (requirements, design, roadmap, implement) and the 7 retired stage ids (roadmap-exit, review-roadmap, requirements-exit, design-exit, review-design-space, review-iteration-plan, plan-iterations) are no longer valid anywhere — the sizing block that consumed the phase names was retired the same ruling.",
|
||||
"enum": ["review-dag", "review-code", "verify", "audit-process", "merge-pr"]
|
||||
},
|
||||
"entry": {
|
||||
@@ -56,20 +56,20 @@
|
||||
"preflight": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/preflightItem" },
|
||||
"description": "Evidence-based producer pre-flight checklist ([org-internal #2599]) 鈥?landed by a human from retrospective PRE-FLIGHT PROPOSAL rows. Absent/empty = no injection. Cap is governed by preflight.max_items (policy), not the schema."
|
||||
"description": "Evidence-based producer pre-flight checklist ([org-internal #2599]) — landed by a human from retrospective PRE-FLIGHT PROPOSAL rows. Absent/empty = no injection. Cap is governed by preflight.max_items (policy), not the schema."
|
||||
},
|
||||
"notes": { "type": "string" },
|
||||
"effort": {
|
||||
"type": "string",
|
||||
"enum": ["low", "high", "max"],
|
||||
"description": "Reasoning-effort hint for this route (llm-latency phase 2): maps onto the active model's variant table when it exposes one (GLM-5.2+ low/high/max); fail-open otherwise. Reserve `low` for genuinely lightweight routes (direct-edit, no pipeline) 鈥?reasoning dominates GLM-5.3 generation latency."
|
||||
"description": "Reasoning-effort hint for this route (llm-latency phase 2): maps onto the active model's variant table when it exposes one (GLM-5.2+ low/high/max); fail-open otherwise. Reserve `low` for genuinely lightweight routes (direct-edit, no pipeline) — reasoning dominates GLM-5.3 generation latency."
|
||||
}
|
||||
}
|
||||
},
|
||||
"preflightPolicy": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Thresholds for the retro 鈫?routes.{Kind}.preflight evidence loop ([org-internal #2599]). Authoritative values live here; the retrospective consults this block, it does not restate the numbers.",
|
||||
"description": "Thresholds for the retro → routes.{Kind}.preflight evidence loop ([org-internal #2599]). Authoritative values live here; the retrospective consults this block, it does not restate the numbers.",
|
||||
"properties": {
|
||||
"max_items": { "type": "integer", "minimum": 1 },
|
||||
"min_sample": { "type": "integer", "minimum": 0 },
|
||||
@@ -86,7 +86,7 @@
|
||||
"item": { "type": "string", "description": "One checklist line the Producer verifies before implementing." },
|
||||
"evidence": {
|
||||
"type": "string",
|
||||
"description": "Retro reference justifying the item, e.g. 'retro [org-internal #2599]: COR 脳 unhandled-null, 5/8 first-round FAILs on Kind/Bug'."
|
||||
"description": "Retro reference justifying the item, e.g. 'retro [org-internal #2599]: COR × unhandled-null, 5/8 first-round FAILs on Kind/Bug'."
|
||||
},
|
||||
"added_cycle": {
|
||||
"type": "string",
|
||||
@@ -97,7 +97,7 @@
|
||||
"filing": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Filing policy (绔嬫/鐧昏绛栫暐). sprint-mode ([org-internal #3007]): when true, verify Phase 5.5 promotes registry rows to tickets in sprint mode only. work-ticket-wip-cap ([org-internal #3005] G3): open non-TD work tickets beyond this count require scheduling evidence, else they stay registry rows.",
|
||||
"description": "Filing policy (立案/登记策略). sprint-mode ([org-internal #3007]): when true, verify Phase 5.5 promotes registry rows to tickets in sprint mode only. work-ticket-wip-cap ([org-internal #3005] G3): open non-TD work tickets beyond this count require scheduling evidence, else they stay registry rows.",
|
||||
"properties": {
|
||||
"sprint-mode": {
|
||||
"type": "boolean",
|
||||
@@ -113,7 +113,7 @@
|
||||
"review": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Review round budget (process-diet P0b, [org-internal #3567]/TD-564): a tail cut, not the norm 鈥?at budget without convergence, STOP; residual findings flow to the TD registry. High risk = a BLOCKER survived a round or the diff touches a frozen cross-session contract.",
|
||||
"description": "Review round budget (process-diet P0b, [org-internal #3567]/TD-564): a tail cut, not the norm — at budget without convergence, STOP; residual findings flow to the TD registry. High risk = a BLOCKER survived a round or the diff touches a frozen cross-session contract.",
|
||||
"required": ["max_rounds"],
|
||||
"properties": {
|
||||
"max_rounds": {
|
||||
@@ -152,7 +152,7 @@
|
||||
"preflight": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/preflightItem" },
|
||||
"description": "Evidence-based pre-flight checklist for the DAG route ([org-internal #2905] 鏂规 3) 鈥?same item shape as routes.{Kind}.preflight ([org-internal #2599]). Landed by a human from retrospective PRE-FLIGHT PROPOSAL rows, including the 'REQ 脳 late-discovery' root cause (requirements found only after the DAG froze: DAG oversize signals, demo-period feedback). Consumed by analyze-dag (the route's entry skill) before decomposition. Absent/empty = no injection. Optional 鈥?deliberately NOT in dagRoute.required (spec-02 搂2.8 frozen required array unchanged). Cap governed by preflight.max_items (policy), not the schema."
|
||||
"description": "Evidence-based pre-flight checklist for the DAG route ([org-internal #2905] 方案 3) — same item shape as routes.{Kind}.preflight ([org-internal #2599]). Landed by a human from retrospective PRE-FLIGHT PROPOSAL rows, including the 'REQ × late-discovery' root cause (requirements found only after the DAG froze: DAG oversize signals, demo-period feedback). Consumed by analyze-dag (the route's entry skill) before decomposition. Absent/empty = no injection. Optional — deliberately NOT in dagRoute.required (spec-02 §2.8 frozen required array unchanged). Cap governed by preflight.max_items (policy), not the schema."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -183,7 +183,7 @@
|
||||
"replaces": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Historical documentation of which legacy gates the single gate replaced ([org-internal #3072] phase 3 retired them) 鈥?free-form strings, not the live stage vocabulary."
|
||||
"description": "Historical documentation of which legacy gates the single gate replaced ([org-internal #3072] phase 3 retired them) — free-form strings, not the live stage vocabulary."
|
||||
},
|
||||
"depth_from": { "type": "string", "enum": ["size_derivation"] },
|
||||
"never_trim": { "type": "boolean" }
|
||||
@@ -201,7 +201,7 @@
|
||||
"items": { "type": "string", "enum": ["Kind/Epic", "Kind/Feature"] },
|
||||
"uniqueItems": true,
|
||||
"minItems": 1,
|
||||
"description": "Ticket kinds whose DAG children reference them as ## 鐖剁骇 / Parent ([org-internal #3061] Phase 2 / TD-388: Kind/Feature added alongside Kind/Epic)."
|
||||
"description": "Ticket kinds whose DAG children reference them as ## 父级 / Parent ([org-internal #3061] Phase 2 / TD-388: Kind/Feature added alongside Kind/Epic)."
|
||||
},
|
||||
"parent_aggregator": { "type": "boolean" }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: gitea-rest
|
||||
description: Use ONLY when an agent must call the Gitea REST API over HTTP (curl / fetch / HttpClient) 鈥?not for other gitea tooling (the retired gitea-mcp wrapper / gitea_* MCP tools). Covers auth schemes + token scopes, pagination, error format, this fork's specifics, copy-paste recipes for the daily loop (issues, PRs, comments, labels, releases, raw files), and the on-demand recipe for extracting any endpoint's definition from the auto-generated swagger spec (`/swagger.v1.json`) instead of keeping an endpoint catalog in context. This is the only supported Gitea API path 鈥?the gitea-mcp wrapper is retired.
|
||||
description: Use ONLY when an agent must call the Gitea REST API over HTTP (curl / fetch / HttpClient) — not for other gitea tooling (the retired gitea-mcp wrapper / gitea_* MCP tools). Covers auth schemes + token scopes, pagination, error format, this fork's specifics, copy-paste recipes for the daily loop (issues, PRs, comments, labels, releases, raw files), and the on-demand recipe for extracting any endpoint's definition from the auto-generated swagger spec (`/swagger.v1.json`) instead of keeping an endpoint catalog in context. This is the only supported Gitea API path — the gitea-mcp wrapper is retired.
|
||||
triggers:
|
||||
# Direct Gitea HTTP/REST. Composite "gitea rest"/"gitea api" to avoid
|
||||
# colliding with other gitea tooling.
|
||||
@@ -11,20 +11,20 @@ triggers:
|
||||
- /api/v1
|
||||
- gitea swagger
|
||||
- swagger.v1.json
|
||||
- 璋?gitea api
|
||||
- gitea 鎺ュ彛
|
||||
- 调gitea api
|
||||
- gitea 接口
|
||||
- gitea rest api
|
||||
role: Producer
|
||||
---
|
||||
|
||||
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛夈€傛湰鏂囦欢鍚屾椂浣滀负 gitea adapter 鐨?**reference implementation** 绀轰緥锛坒rontmatter 鍘熸牱锛夛紱瀹炰緥鏈瀵圭収 `core/adapters/TERMINOLOGY.md`銆?
|
||||
# Gitea REST 鈥?conventions + on-demand endpoint lookup
|
||||
> Core 中立版(Increment 6a 改写)。本文件同时作为 gitea adapter 的 **reference implementation** 示例(frontmatter 原样);实例术语对照 `core/adapters/TERMINOLOGY.md`。
|
||||
# Gitea REST — conventions + on-demand endpoint lookup
|
||||
|
||||
Gitea ships a JSON REST API under **`/api/v1`**. The instance also publishes an
|
||||
**auto-generated OpenAPI spec at `/swagger.v1.json`** (~889KB, 316 endpoints) 鈥?
|
||||
**auto-generated OpenAPI spec at `/swagger.v1.json`** (~889KB, 316 endpoints) —
|
||||
never load it whole into context; extract single endpoint definitions with jq
|
||||
(see "Endpoint lookup"). The spec is generated from this fork's code, so it is
|
||||
always current 鈥?no manually maintained catalog can drift.
|
||||
always current — no manually maintained catalog can drift.
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -37,7 +37,7 @@ curl -fsS -H "$AUTH" $BASE/api/v1/version # => {"version":"1.22.0"}
|
||||
|
||||
Inside a workspace container: `WORKSPACE_GIT_TOKEN` already carries the
|
||||
provisioned token, and the instance host resolves to the VPC-internal address
|
||||
(no EIP bandwidth cap) 鈥?use it as `TOKEN` directly.
|
||||
(no EIP bandwidth cap) — use it as `TOKEN` directly.
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -47,7 +47,7 @@ provisioned token, and the instance host resolves to the VPC-internal address
|
||||
| OAuth2 JWT | `Authorization: bearer <jwt>` | OAuth2-app token. |
|
||||
| Basic | `Authorization: Basic <base64(user:pass)>` | Server must enable it; 2FA needs `X-Gitea-OTP`. |
|
||||
|
||||
- Query-param token (`?token=`) is deprecated 鈥?avoid.
|
||||
- Query-param token (`?token=`) is deprecated — avoid.
|
||||
- `Sudo: <username>` header acts as another user (admin only).
|
||||
- Keep tokens out of git and out of process argv where possible: read from a
|
||||
file/env into a shell var at call time, not in a committed script.
|
||||
@@ -55,21 +55,21 @@ provisioned token, and the instance host resolves to the VPC-internal address
|
||||
## Token scopes (write implies read)
|
||||
|
||||
Categories: `issue`, `repository`, `organization`, `user`, `notification`,
|
||||
`package`, `admin`, `misc`, `activitypub` 鈥?each as `read:X` / `write:X`.
|
||||
HTTP method sets the level: **GET 鈫?read**, **POST/PUT/PATCH/DELETE 鈫?write**.
|
||||
`package`, `admin`, `misc`, `activitypub` — each as `read:X` / `write:X`.
|
||||
HTTP method sets the level: **GET → read**, **POST/PUT/PATCH/DELETE → write**.
|
||||
A scope miss returns `403` with `token does not have at least one of required
|
||||
scope(s)...` 鈥?re-issue the token rather than widening other permissions.
|
||||
scope(s)...` — re-issue the token rather than widening other permissions.
|
||||
|
||||
The full agent loop (push commits, create/merge PRs, comment) needs at minimum
|
||||
**`write:repository` + `write:issue`**. Workspace container tokens are
|
||||
provisioned `read:repository` by default 鈥?write operations fail with 403
|
||||
provisioned `read:repository` by default — write operations fail with 403
|
||||
until the operator widens the scope.
|
||||
|
||||
## Pagination
|
||||
|
||||
- `page` (1-based, default 1), `limit` (default 30, **hard cap 50**).
|
||||
- `X-Total-Count` response header = total results.
|
||||
- `Link: <url?page=2>; rel="next"` 鈥?walk `next` links until absent.
|
||||
- `Link: <url?page=2>; rel="next"` — walk `next` links until absent.
|
||||
- Some endpoints (commits, pulls) also set `X-Page` / `X-PerPage` /
|
||||
`X-PageCount` / `X-HasMore`.
|
||||
|
||||
@@ -78,12 +78,12 @@ until the operator widens the scope.
|
||||
Every error is `{"message":"...","url":".../api/swagger"}` with the matching
|
||||
status: `400` bad arg, `403` permission/scope, `404` not found or wrong BASE,
|
||||
`409` already exists, `413` too large, `422` malformed JSON / missing field.
|
||||
There is **no rate limiting** on `/api/v1` 鈥?don't expect `X-RateLimit-*`.
|
||||
There is **no rate limiting** on `/api/v1` — don't expect `X-RateLimit-*`.
|
||||
|
||||
## Endpoint lookup (swagger, on demand)
|
||||
|
||||
For any endpoint not covered by the recipes below, pull the spec once per
|
||||
session and jq out just the definition you need (50鈥?00 tokens each):
|
||||
session and jq out just the definition you need (50–200 tokens each):
|
||||
|
||||
```sh
|
||||
curl -s $BASE/swagger.v1.json -o /tmp/gitea-sw.json
|
||||
@@ -109,7 +109,7 @@ curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/issues" -H "$AUTH" -H 'Conten
|
||||
-d '{"title":"Bug: X fails","body":"steps...","labels":[12]}'
|
||||
```
|
||||
|
||||
**Comment on an issue / PR** (same path for both 鈥?PRs are issues by index)
|
||||
**Comment on an issue / PR** (same path for both — PRs are issues by index)
|
||||
|
||||
```sh
|
||||
curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/issues/42/comments" -H "$AUTH" \
|
||||
@@ -130,48 +130,48 @@ curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls/7/merge" -H "$AUTH" -H
|
||||
-d '{"Do":"merge","MergeTitleField":"feat: add X","MergeMessageField":"closes #7"}'
|
||||
```
|
||||
|
||||
**Merge returned 405? Read the response body 鈥?two distinct causes:**
|
||||
**Merge returned 405? Read the response body — two distinct causes:**
|
||||
|
||||
- `{"message":"head branch is behind the base branch"}` 鈥?the keep-mergeable
|
||||
- `{"message":"head branch is behind the base branch"}` — the keep-mergeable
|
||||
bot advanced main after your push, or Gitea's mergeable cache is stale.
|
||||
Fix: `git fetch origin main && git merge origin/main && git push` the head
|
||||
branch, wait ~2s, retry the merge. (Recurring on this server 鈥?seen 3 PRs
|
||||
branch, wait ~2s, retry the merge. (Recurring on this server — seen 3 PRs
|
||||
in a row on 2026-08-23.)
|
||||
- `{"message":"The PR is already merged"}` 鈥?**treat as success**. The merge
|
||||
- `{"message":"The PR is already merged"}` — **treat as success**. The merge
|
||||
landed during a previous "failed" attempt (racy async recompute). Confirm
|
||||
with `GET .../pulls/7 | jq '.merged, .merge_commit_sha'` before assuming
|
||||
failure. Never re-push/re-create the PR on this signal.
|
||||
|
||||
**Auth hygiene** 鈥?always pass the token via the `Authorization` header
|
||||
**Auth hygiene** — always pass the token via the `Authorization` header
|
||||
(`-H "Authorization: token $TOKEN"`), never embedded in a remote URL
|
||||
(`https://user:token@host/...`): it persists into `.git/config` and shell
|
||||
history. If a token leaks into a remote URL, rewrite the remote
|
||||
(`git remote set-url`) and rotate.
|
||||
|
||||
**List PRs / changed files** 鈥?`GET .../pulls?state=open`, `GET .../pulls/7/files`
|
||||
**List PRs / changed files** — `GET .../pulls?state=open`, `GET .../pulls/7/files`
|
||||
|
||||
**Search issues across repos** 鈥?`GET $BASE/api/v1/repos/issues/search?q=&type=pulls`
|
||||
**Search issues across repos** — `GET $BASE/api/v1/repos/issues/search?q=&type=pulls`
|
||||
|
||||
**List labels** 鈥?`GET .../labels?limit=50 | jq '.[] | {name,color}'` (label create/update via POST/PATCH on the same path)
|
||||
**List labels** — `GET .../labels?limit=50 | jq '.[] | {name,color}'` (label create/update via POST/PATCH on the same path)
|
||||
|
||||
**Raw file contents** 鈥?`GET .../raw/README.md` (plain text; the `contents/{path}` variant returns base64 in `.content`)
|
||||
**Raw file contents** — `GET .../raw/README.md` (plain text; the `contents/{path}` variant returns base64 in `.content`)
|
||||
|
||||
**Create a release** 鈥?`POST .../releases {"tag_name":"v1.2.0","target":"main",...}`
|
||||
**Create a release** — `POST .../releases {"tag_name":"v1.2.0","target":"main",...}`
|
||||
|
||||
**Create a wiki page** 鈥?`POST .../wiki/new {"title":"My Page","content_base64":"<b64>","message":"add page"}`. **`content_base64` is the ONLY field that writes body text** 鈥?this fork **silently ignores** the upstream-style `content` field on wiki create/update (no error, `201` returned, page saved 0 bytes; evidence [org-internal #3944]: commits f5eaf18/4f0217d/bad5880). `content` is valid only on the file API, never on wiki.
|
||||
**Create a wiki page** — `POST .../wiki/new {"title":"My Page","content_base64":"<b64>","message":"add page"}`. **`content_base64` is the ONLY field that writes body text** — this fork **silently ignores** the upstream-style `content` field on wiki create/update (no error, `201` returned, page saved 0 bytes; evidence [org-internal #3944]: commits f5eaf18/4f0217d/bad5880). `content` is valid only on the file API, never on wiki.
|
||||
|
||||
**Read / edit / delete a wiki page** 鈥?`GET|PATCH|DELETE .../wiki/page/{pageName}`. **Read by the mangled name, not the logical title**: this fork rewrites stored filenames for titles containing `/`, spaces, or `:` (slash percent-encoded into the filename + a `.-` suffix 鈥?see [org-internal #3218]), so `GET /wiki/page/bugfix-3204/bugfix-report` 404s. Always `GET .../wiki/pages` first and use the returned `sub_url` **verbatim** (it already carries the `%2F` encoding and `.-` suffix). PATCH/DELETE take the same mangled `pageName`. For PATCH, **omit `title` to keep the page name** and send only `content_base64`+`message` (fixed in gitea `dev-421-g7ff56aec08`, [org-internal #3510] 鈥?before that a title-less PATCH silently renamed the page to `unnamed.md` and later edits deleted target pages). Same trap as create: a PATCH carrying `content` instead of `content_base64` returns `200` with a **0-byte page** ([org-internal #3944]) 鈥?always base64-encode the body and verify non-empty via a follow-up `GET`.
|
||||
**Read / edit / delete a wiki page** — `GET|PATCH|DELETE .../wiki/page/{pageName}`. **Read by the mangled name, not the logical title**: this fork rewrites stored filenames for titles containing `/`, spaces, or `:` (slash percent-encoded into the filename + a `.-` suffix — see [org-internal #3218]), so `GET /wiki/page/bugfix-3204/bugfix-report` 404s. Always `GET .../wiki/pages` first and use the returned `sub_url` **verbatim** (it already carries the `%2F` encoding and `.-` suffix). PATCH/DELETE take the same mangled `pageName`. For PATCH, **omit `title` to keep the page name** and send only `content_base64`+`message` (fixed in gitea `dev-421-g7ff56aec08`, [org-internal #3510] — before that a title-less PATCH silently renamed the page to `unnamed.md` and later edits deleted target pages). Same trap as create: a PATCH carrying `content` instead of `content_base64` returns `200` with a **0-byte page** ([org-internal #3944]) — always base64-encode the body and verify non-empty via a follow-up `GET`.
|
||||
|
||||
## This fork's specifics
|
||||
|
||||
- **Projects are repo-level only** 鈥?no `/orgs/.../projects` or `/users/.../projects` REST endpoints.
|
||||
- **Aggregated inline review comments**: no single list-all endpoint 鈥?list
|
||||
- **Projects are repo-level only** — no `/orgs/.../projects` or `/users/.../projects` REST endpoints.
|
||||
- **Aggregated inline review comments**: no single list-all endpoint — list
|
||||
reviews, then fetch each review's `/comments`.
|
||||
- Wiki REST = 6 method脳path combos on 4 paths (`new`, `page/{pageName}`
|
||||
- Wiki REST = 6 method×path combos on 4 paths (`new`, `page/{pageName}`
|
||||
GET/PATCH/DELETE, `pages`, `revisions/{pageName}`). This fork has **no**
|
||||
upstream `raw/{pageName}` or `pages/{pageName}` single-page endpoints, and
|
||||
`page/{pageName}` only matches the mangled filename (see the wiki recipe
|
||||
above and [org-internal #3218]) 鈥?clone the wiki git repo (default branch `main`) only if
|
||||
above and [org-internal #3218]) — clone the wiki git repo (default branch `main`) only if
|
||||
you need history beyond `/wiki/revisions` or bulk filename surgery.
|
||||
- The authoritative route table is `routers/api/v1/api.go` in the Gitea
|
||||
source; the human-readable docs UI is `<BASE>/api/swagger`.
|
||||
@@ -180,4 +180,4 @@ history. If a token leaks into a remote URL, rewrite the remote
|
||||
|
||||
Prefer `HttpClient` from `@effect/platform` with the same headers (see
|
||||
`rules/effect-rules` on the wiki). Outside Effect, `curl` / `Bun.fetch` are
|
||||
fine 鈥?the wire format is identical.
|
||||
fine — the wire format is identical.
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
---
|
||||
name: headless-session-ops
|
||||
description: >
|
||||
Use ONLY when an agent must autonomously launch a headless main session 鈥?
|
||||
Use ONLY when an agent must autonomously launch a headless main session —
|
||||
create a session and drive agent generation over the HTTP `prompt_async`
|
||||
endpoint with NO persistent client attached (Web UI / TUI absent) 鈥?OR
|
||||
endpoint with NO persistent client attached (Web UI / TUI absent) — OR
|
||||
lifecycle-manage such a session afterwards: health-check polling, hung-stream
|
||||
diagnosis (state=generating + frozen message count), abort + re-wake rescue,
|
||||
provider quota-wall recovery (worker killed mid-task by a usage limit 鈥?
|
||||
provider quota-wall recovery (worker killed mid-task by a usage limit —
|
||||
detect, re-wake on a known-good provider, inject facts), and fact-baseline
|
||||
injection to correct a stale worldview in a woken worker.
|
||||
This is the path used by in-session agents spawning sibling sessions,
|
||||
`octopus run --attach` non-interactive mode, and cron / CI drivers. Covers
|
||||
the 3-step flow (create session 鈫?POST prompt_async WITH a known-good
|
||||
`model` 鈫?poll `/session/:id/message` to verify generation), how to REUSE
|
||||
the 3-step flow (create session → POST prompt_async WITH a known-good
|
||||
`model` → poll `/session/:id/message` to verify generation), how to REUSE
|
||||
the current session's model for the new session, the model-field
|
||||
requirement, the directory default (current session's directory unless
|
||||
targeting another repo), and the ticket-driven recipe (launch a session
|
||||
seeded from issue #N). Do NOT use when a persistent client (Web UI / TUI)
|
||||
is driving the session 鈥?those use the synchronous `prompt` endpoint and
|
||||
is driving the session — those use the synchronous `prompt` endpoint and
|
||||
always send `model`.
|
||||
triggers:
|
||||
# English 鈥?the brand phrases for headless launch.
|
||||
# English — the brand phrases for headless launch.
|
||||
- headless session
|
||||
- launch session
|
||||
- prompt_async
|
||||
- prompt async
|
||||
- autonomous session
|
||||
# English 鈥?the failure mode (agent sees a stalled session and needs to know why).
|
||||
# English — the failure mode (agent sees a stalled session and needs to know why).
|
||||
- session not generating
|
||||
- session generation stuck
|
||||
# English 鈥?ticket-driven launch.
|
||||
# English — ticket-driven launch.
|
||||
- launch session for issue
|
||||
- launch session for ticket
|
||||
- spawn session for ticket
|
||||
# English 鈥?patrol mode (delayed self-wake polling loops, [org-internal #3937]).
|
||||
# English — patrol mode (delayed self-wake polling loops, [org-internal #3937]).
|
||||
- patrol loop
|
||||
- patrol mode
|
||||
- delayed wake
|
||||
# Chinese 鈥?bare noun phrases (matches() is a contiguous substring).
|
||||
- 鎷夎捣涓讳細璇?
|
||||
- 鎷夎捣浼氳瘽
|
||||
- 鑷不浼氳瘽
|
||||
- 鏃犲ご浼氳瘽
|
||||
# Chinese 鈥?patrol mode ([org-internal #3937]).
|
||||
- 宸℃妯″紡
|
||||
- 寤惰繜鍞ら啋
|
||||
- 浼氳瘽涓嶇敓鎴?
|
||||
- 浼氳瘽鍗℃
|
||||
- 涓哄伐鍗曟媺璧蜂細璇?
|
||||
- 宸ュ崟鎷夎捣浼氳瘽
|
||||
# English 鈥?lifecycle (hung rescue, stale-worldview correction, [org-internal #2459]).
|
||||
# Chinese — bare noun phrases (matches() is a contiguous substring).
|
||||
- 拉起主会话
|
||||
- 拉起会话
|
||||
- 自治会话
|
||||
- 无头会话
|
||||
# Chinese — patrol mode ([org-internal #3937]).
|
||||
- 巡检模式
|
||||
- 延迟唤醒
|
||||
- 会话不生成
|
||||
- 会话卡死
|
||||
- 为工单拉起会话
|
||||
- 工单拉起会话
|
||||
# English — lifecycle (hung rescue, stale-worldview correction, [org-internal #2459]).
|
||||
- abort session
|
||||
- revive session
|
||||
- session rescue
|
||||
- fact baseline
|
||||
# English 鈥?quota-wall recovery ([org-internal #3669]): worker killed by provider usage limit.
|
||||
# English — quota-wall recovery ([org-internal #3669]): worker killed by provider usage limit.
|
||||
- quota wall
|
||||
- usage limit reached
|
||||
- quota exhausted
|
||||
- switch provider
|
||||
# Chinese 鈥?lifecycle ops ([org-internal #2459]).
|
||||
- 鍞ら啋浼氳瘽
|
||||
- 浼氳瘽鎸傛
|
||||
- 浜嬪疄鍩虹嚎
|
||||
# Chinese 鈥?quota-wall recovery ([org-internal #3669]).
|
||||
- 閰嶉澧?
|
||||
- 閰嶉鑰楀敖
|
||||
- 鐢ㄩ噺涓婇檺
|
||||
- 鎹緵搴斿晢
|
||||
# Chinese — lifecycle ops ([org-internal #2459]).
|
||||
- 唤醒会话
|
||||
- 会话挂死
|
||||
- 事实基线
|
||||
# Chinese — quota-wall recovery ([org-internal #3669]).
|
||||
- 配额墙
|
||||
- 配额耗尽
|
||||
- 用量上限
|
||||
- 换供应商
|
||||
role: Producer
|
||||
---
|
||||
|
||||
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛屽師 deferHard verbatimDir锛夈€傛満鍒躲€佺粨鏋勪笌 frontmatter 淇濇寔锛涘疄渚嬫湳璇紙宸ュ叿鍚嶃€佽矾寰勩€佸伐鍗曞彿锛夋寜 `core/adapters/TERMINOLOGY.md` 缁戝畾鍒板叿浣撳疄渚嬨€?
|
||||
> Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。
|
||||
# Headless Main Session Launch (prompt_async)
|
||||
|
||||
Launch a **main session for a specific issue / work item from a headless
|
||||
context** 鈥?no Web UI, no TUI, just HTTP. This is the "fire-and-forget" path: an
|
||||
context** — no Web UI, no TUI, just HTTP. This is the "fire-and-forget" path: an
|
||||
agent inside another session, a cron job, a CI script, or `octopus run --attach`
|
||||
non-interactive mode all land here when they need to spin up a session that
|
||||
actually generates.
|
||||
|
||||
## Directory convention 鈥?independent session in the current workspace, no worktree
|
||||
## Directory convention — independent session in the current workspace, no worktree
|
||||
|
||||
The default is an **independent session sharing the current session's
|
||||
directory** 鈥?the repo you are already working in. No new worktree, no
|
||||
directory** — the repo you are already working in. No new worktree, no
|
||||
checkout, no extra `bun install` or `.codegraph/` rebuild. Session creation
|
||||
and worktree discipline are **orthogonal**: this skill only opens a session
|
||||
record pointed at a directory; whether the launched session later creates its
|
||||
own worktree is a _workflow_ decision (per the worktree-discipline rule) it
|
||||
makes when it starts actual work 鈥?not something the launcher does. One
|
||||
makes when it starts actual work — not something the launcher does. One
|
||||
exception: the **ticket-driven recipe** below, where claim-first ([org-internal #2297])
|
||||
requires the launcher to push the workflow branch before launch, making a
|
||||
pre-built worktree the claim carrier + ready workspace (see Pitfall #10
|
||||
@@ -100,7 +100,7 @@ carve-out). So, unless a different repo / path is specifically targeted,
|
||||
from your own session record via `GET /session/$LAUNCHER_SID`). The new
|
||||
session runs independently in that same workspace. Pass an explicit
|
||||
different `?directory=` only when the new session must work in another
|
||||
**repo** 鈥?never a worktree of the same repo. The Web UI sidebar groups
|
||||
**repo** — never a worktree of the same repo. The Web UI sidebar groups
|
||||
sessions by directory (`sidebar-project.tsx` per-workspace
|
||||
`workspaceSessions(directory)`, fetched via `session.list({ directory })`),
|
||||
so a session pointed at a worktree disappears from the workspace the creator
|
||||
@@ -109,23 +109,23 @@ carve-out). So, unless a different repo / path is specifically targeted,
|
||||
- **No current session** (cron / CI / bare script): the server-side fallback
|
||||
when `?directory=` is omitted is the **server process's `process.cwd()`**
|
||||
(`server/routes/instance/httpapi/middleware/workspace-routing.ts:76`), which
|
||||
is wherever the server was started 鈥?unreliable. In that context `?directory=`
|
||||
is wherever the server was started — unreliable. In that context `?directory=`
|
||||
remains effectively required.
|
||||
- **Dispatch convention 鈥?directory = code location, not tracker repo.** When
|
||||
- **Dispatch convention — directory = code location, not tracker repo.** When
|
||||
launching a session for a ticket, set `?directory=` to the **main workspace**
|
||||
checkout that holds the **code the ticket edits**, which may differ from the
|
||||
repo where the ticket is tracked. Example: `<owner>/<backend-repo>#<n>` is filed in
|
||||
the ticket-tracker repo but edits the main repo's workflow files 鈫?the
|
||||
the ticket-tracker repo but edits the main repo's workflow files → the
|
||||
session's `?directory=` is the **code checkout root**
|
||||
(`<workspace-root>/<org>/<repo>`), not the tracker repo checkout. Never point
|
||||
`?directory=` at a ticket worktree 鈥?the worktree goes in the claim comment +
|
||||
`?directory=` at a ticket worktree — the worktree goes in the claim comment +
|
||||
seed prompt, not the directory field (sidebar visibility, see bullet 1). The
|
||||
owner-check (`GET /session?directory=$DIR`) is scoped per directory, so
|
||||
rooting the session at the correct code checkout is also what makes the
|
||||
duplicate-session guard meaningful. Server-side backstop ([org-internal #3190]): creating a
|
||||
session whose directory IS a linked git worktree answers with an
|
||||
`X-Session-Directory-Warning` response header and a server warn log naming
|
||||
the owning main repo 鈥?if you see that header, the session will be invisible
|
||||
the owning main repo — if you see that header, the session will be invisible
|
||||
to `GET /session?directory=<mainRepo>` owner-checks; re-check your launch
|
||||
parameters before proceeding.
|
||||
|
||||
@@ -135,10 +135,10 @@ carve-out). So, unless a different repo / path is specifically targeted,
|
||||
> corrected the misdiagnosed [org-internal #1691], see comment 9205). Code citations below were
|
||||
> verified against `<harness-package>` at HEAD.
|
||||
|
||||
## The `model` field 鈥?always provide a known-good model (read this first)
|
||||
## The `model` field — always provide a known-good model (read this first)
|
||||
|
||||
`prompt_async` is **fire-and-forget**: after HTTP returns 204 (or 202 when the
|
||||
message is queued behind a wedge/zombie session 鈥?no live runner in that
|
||||
message is queued behind a wedge/zombie session — no live runner in that
|
||||
process), the server runs the full agent loop in a separate fiber. **Always
|
||||
include an explicit `model`
|
||||
in the payload, and reuse the current session's model for it** (see the next
|
||||
@@ -154,7 +154,7 @@ section). Two reasons:
|
||||
// ^^^^^^^^^ agent ^^^^^^^^^^^^^^^^^ session/default
|
||||
```
|
||||
|
||||
So omission does **not** produce a clean validation error 鈥?it silently
|
||||
So omission does **not** produce a clean validation error — it silently
|
||||
resolves to the agent's model, the session's last-used model, or the
|
||||
provider default (`currentModel`, `prompt.ts:824-838`).
|
||||
|
||||
@@ -164,15 +164,15 @@ section). Two reasons:
|
||||
(`prompt.ts:1399`, `:804-822`). In the **async** handler that error is caught
|
||||
by the delivery fork's failure handling (`forkPromptDelivery` in
|
||||
`handlers/session.ts`) and never reaches the HTTP
|
||||
caller 鈥?so the session looks healthy (HTTP 204, user message persisted) yet
|
||||
caller — so the session looks healthy (HTTP 204, user message persisted) yet
|
||||
**never generates**. This is exactly the "silent stall" observed empirically
|
||||
in [org-internal #1695]: a session with a 1-message transcript (user only) and frozen
|
||||
`time.updated`.
|
||||
|
||||
| payload | result |
|
||||
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `{agent, parts}` 鈥?no `model` | 204/202; model filled by fallback; if the resolved model is unusable 鈫?**apparent silent stall** (no error surfaced, no generation). |
|
||||
| `{agent, model:{providerID,modelID}, parts}` | 204/202; deterministic generation within seconds (verified: "1+1 绛変簬 2銆? in ~6s). |
|
||||
| `{agent, parts}` — no `model` | 204/202; model filled by fallback; if the resolved model is unusable → **apparent silent stall** (no error surfaced, no generation). |
|
||||
| `{agent, model:{providerID,modelID}, parts}` | 204/202; deterministic generation within seconds (verified: "1+1 等于 2。 in ~6s). |
|
||||
|
||||
> **Iron Law:** every `prompt_async` payload MUST carry a **known-good** `model`,
|
||||
> and the safest known-good model is **the one driving this session right now**
|
||||
@@ -181,14 +181,14 @@ section). Two reasons:
|
||||
|
||||
**Field-name gotcha when copying a model:** the prompt payload's `ModelRef` uses
|
||||
`{ providerID, modelID }` (`prompt.ts:1852-1855`), but the **session record's**
|
||||
model uses `{ id, providerID, variant }` (`session.ts:256-260`) 鈥?i.e. `modelID`
|
||||
model uses `{ id, providerID, variant }` (`session.ts:256-260`) — i.e. `modelID`
|
||||
is called `id`. When you reuse a model read from a session record, remap
|
||||
`model.id 鈫?modelID` (see recipe below). User-message `info.model` already uses
|
||||
`model.id → modelID` (see recipe below). User-message `info.model` already uses
|
||||
`modelID` (`message.ts:403-407`).
|
||||
|
||||
## Reuse the current session's model (recommended default)
|
||||
|
||||
The model powering the launching session is proven to work 鈥?reuse it. Discover
|
||||
The model powering the launching session is proven to work — reuse it. Discover
|
||||
it by whichever path fits your context:
|
||||
|
||||
- **You are an in-session agent** (most common): read it straight from your own
|
||||
@@ -196,27 +196,27 @@ it by whichever path fits your context:
|
||||
|
||||
> "You are powered by the model named **{modelID}** (provider: **{providerID}**)."
|
||||
|
||||
e.g. `modelID = "glm-5.2"`, `providerID = "zai-coding-plan"`锛堟浘鍚?
|
||||
zhipuai-coding-plan锛?026-08 鏇村悕锛?
|
||||
e.g. `modelID = "glm-5.2"`, `providerID = "zai-coding-plan"`(曾名
|
||||
zhipuai-coding-plan,2026-08 更名)
|
||||
|
||||
- **Programmatic / no system prompt** (cron, CI, sibling caller): `GET` the
|
||||
launching session's record and read `.model`:
|
||||
|
||||
```sh
|
||||
curl -s "$BASE/session/$LAUNCHER_SID?directory=$DIR" \
|
||||
| jq '.model' # 鈫?{ "id": "glm-5.2", "providerID": "zai-coding-plan", "variant": ... }
|
||||
| jq '.model' # → { "id": "glm-5.2", "providerID": "zai-coding-plan", "variant": ... }
|
||||
```
|
||||
|
||||
Then **remap** `model.id 鈫?modelID` when building the payload.
|
||||
Then **remap** `model.id → modelID` when building the payload.
|
||||
|
||||
- **Verify the provider is actually connected** before launching (cheap
|
||||
insurance 鈥?the model only generates if its provider is live):
|
||||
insurance — the model only generates if its provider is live):
|
||||
```sh
|
||||
curl -s "$BASE/provider?directory=$DIR" | jq '.connected | index("<current providerID>")'
|
||||
# 鈫?null = provider NOT connected (do NOT launch); 0..n = connected
|
||||
# → null = provider NOT connected (do NOT launch); 0..n = connected
|
||||
```
|
||||
or call the `list-models` tool. Note: model-level availability is only
|
||||
knowable at runtime 鈥?a _configured_ model can still fail (subscription-tier
|
||||
knowable at runtime — a _configured_ model can still fail (subscription-tier
|
||||
or region errors); `GET /config/providers` lists _configured_ models, not
|
||||
usable ones. The safest pre-flight is "provider connected" + reusing the
|
||||
current session's model, which is generating by definition. There is **no
|
||||
@@ -226,9 +226,9 @@ it by whichever path fits your context:
|
||||
|
||||
- A reachable octopus HTTP instance (e.g. `http://127.0.0.1:4096`).
|
||||
- The target directory (passed as `?directory=`). Defaults to the current
|
||||
session's directory for in-session launches 鈥?see the Directory convention
|
||||
session's directory for in-session launches — see the Directory convention
|
||||
above; only specify a different one explicitly when targeting another repo.
|
||||
- A known-good `model` (reuse the current session's 鈥?see above). Model IDs
|
||||
- A known-good `model` (reuse the current session's — see above). Model IDs
|
||||
must match the provider config in `<config-home>/octopus/octopus.jsonc`;
|
||||
credentials live in the octopus account keyring (not env), so an isolated
|
||||
HOME cannot reuse them.
|
||||
@@ -242,27 +242,27 @@ BASE=http://127.0.0.1:4096
|
||||
DIR=/data/octopus
|
||||
```
|
||||
|
||||
### Step 1 鈥?Create the session
|
||||
### Step 1 — Create the session
|
||||
|
||||
```sh
|
||||
SID=$(curl -s -X POST "$BASE/session?directory=$DIR" \
|
||||
-H 'content-type: application/json' \
|
||||
-d '{"title":"#N 鈥?<short description>","agent":"builder"}' | jq -r .id)
|
||||
-d '{"title":"#N — <short description>","agent":"builder"}' | jq -r .id)
|
||||
echo "session=$SID"
|
||||
```
|
||||
|
||||
- `POST /session` returns `Session.Info` with `.id` (`groups/session.ts:207-211`).
|
||||
- `?directory=` points at the repo root. For in-session launches it defaults to
|
||||
the **current session's directory** (Directory convention above) 鈥?keep it
|
||||
the **current session's directory** (Directory convention above) — keep it
|
||||
that way so the new session stays visible in the creator's sidebar; pass an
|
||||
explicit path only when targeting a different repo (a worktree of the same
|
||||
repo is NOT a directory target 鈥?hand it over via the seed prompt), and
|
||||
repo is NOT a directory target — hand it over via the seed prompt), and
|
||||
always pass it from cron / CI where no current session exists (server falls
|
||||
back to its own `process.cwd()` otherwise).
|
||||
- `agent` is `builder` (the main agent). Sub-agents (explorer/worker) are
|
||||
spawned within a session 鈥?never named here.
|
||||
spawned within a session — never named here.
|
||||
|
||||
### Step 2 鈥?Deliver the seed prompt (CRITICAL: include a known-good `model`)
|
||||
### Step 2 — Deliver the seed prompt (CRITICAL: include a known-good `model`)
|
||||
|
||||
```sh
|
||||
curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
|
||||
@@ -278,17 +278,17 @@ curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
|
||||
body** when processing starts now (idle session, live runner, noReply
|
||||
injection, or waiting-question preemption), **202** when the message is
|
||||
queued behind a session that is generating with no live runner in this
|
||||
process (wedge/zombie shape 鈥?stored and re-driven on restart drain or the
|
||||
process (wedge/zombie shape — stored and re-driven on restart drain or the
|
||||
next runner), **202** when `delay_sec` defers delivery to an in-memory
|
||||
delayed wake ([org-internal #3937] A 鈥?see "宸℃妯″紡 / Patrol mode" below), and **409**
|
||||
delayed wake ([org-internal #3937] A — see "巡检模式 / Patrol mode" below), and **409**
|
||||
when the session is paused (message NOT stored) or the maintenance-mode
|
||||
hold queue is full.
|
||||
- **Global route alternative ([org-internal #4307]):** when the client only knows the
|
||||
sessionID (no `?directory=` routing), `POST /prompt_async` (no path prefix)
|
||||
resolves the instance from the app database by the body's `sessionID` and
|
||||
delegates to the same delivery core 鈥?same 204/202/409 semantics, same
|
||||
delegates to the same delivery core — same 204/202/409 semantics, same
|
||||
`delay_sec` contract, body is the session-scoped payload plus a required
|
||||
`sessionID` field. Unknown sessionID 鈫?404 naming the session-scoped
|
||||
`sessionID` field. Unknown sessionID → 404 naming the session-scoped
|
||||
route. This is the natural fit for the completion-report POST (fixed port,
|
||||
no directory context).
|
||||
- `delay_sec` (optional, integer 0..86400, else 400) schedules the prompt for
|
||||
@@ -297,16 +297,16 @@ curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
|
||||
at intake. **Precedence ([org-internal #3937] review):** `delay_sec` applies only when the
|
||||
intake would otherwise start processing now (immediate class). For the
|
||||
queued-behind-wedge 202 member and under maintenance queue mode it is
|
||||
stripped 鈥?retention semantics win (the server logs the drop; the delay is
|
||||
NOT honored). Pending wakes are **in-memory only 鈥?a server restart abandons
|
||||
stripped — retention semantics win (the server logs the drop; the delay is
|
||||
NOT honored). Pending wakes are **in-memory only — a server restart abandons
|
||||
them**; a patrol loop must therefore tolerate a missed wake and re-arm.
|
||||
Never use `sleep <big>; curl ...` in a bash tool call to wait 鈥?that blocks
|
||||
Never use `sleep <big>; curl ...` in a bash tool call to wait — that blocks
|
||||
the turn with zero output (the [org-internal #3937] incident shape); the bash tool now
|
||||
warns on bare sleeps > 120s (long-sleep guard, [org-internal #3937] C).
|
||||
- `model` is `{ providerID, modelID }` or the shorthand string
|
||||
`"providerID/modelID"` (split on the FIRST `/`; accepted since [org-internal #4307] 鈥?
|
||||
`"providerID/modelID"` (split on the FIRST `/`; accepted since [org-internal #4307] —
|
||||
e.g. `"zai-coding-plan/glm-5.2"`). **Do not omit it.** Fill the values from
|
||||
your own session's model (see "Reuse the current session's model" above) 鈥?
|
||||
your own session's model (see "Reuse the current session's model" above) —
|
||||
never hardcode a specific model. A string without a `/` is rejected with
|
||||
400 naming both accepted forms.
|
||||
- When the seed prompt contains newlines/quotes, build the payload from a file
|
||||
@@ -315,87 +315,87 @@ curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
|
||||
jq -n --rawfile p seed.txt '{agent:"builder",model:{providerID:"<current providerID>",modelID:"<current modelID>"},parts:[{type:"text",text:$p}]}'
|
||||
```
|
||||
|
||||
### Step 3 鈥?Verify the session actually generated (do NOT assume success)
|
||||
### Step 3 — Verify the session actually generated (do NOT assume success)
|
||||
|
||||
HTTP 204/202 鈮?generation success. Poll until an `assistant` message appears. The
|
||||
endpoint is **`/session/:id/message` 鈥?SINGULAR**, and it returns a bare array
|
||||
HTTP 204/202 ≠generation success. Poll until an `assistant` message appears. The
|
||||
endpoint is **`/session/:id/message` — SINGULAR**, and it returns a bare array
|
||||
whose elements are `{ info, parts }` with `role` at `.info.role`
|
||||
(`groups/session.ts:88,183-194`). Poll the **tail with `limit=1`** 鈥?the
|
||||
no-`limit` form loads the ENTIRE transcript server-side (O(messages脳parts) per
|
||||
(`groups/session.ts:88,183-194`). Poll the **tail with `limit=1`** — the
|
||||
no-`limit` form loads the ENTIRE transcript server-side (O(messages×parts) per
|
||||
poll; pitfall #14's 478-message session paid it on every health check), while
|
||||
`limit=1` returns just the newest message (REQ-F-010 windowing, `message.ts`
|
||||
`page()`):
|
||||
|
||||
```sh
|
||||
# wait 5鈥?0s, then poll the TAIL 鈥?never the full transcript
|
||||
# wait 5–10s, then poll the TAIL — never the full transcript
|
||||
curl -s "$BASE/session/$SID/message?directory=$DIR&limit=1" \
|
||||
| jq '{latest_role: .[0].info.role, latest_id: .[0].info.id}'
|
||||
```
|
||||
|
||||
Verdict: `latest_role == "assistant"` 鈫?the first generation completed (the
|
||||
Verdict: `latest_role == "assistant"` → the first generation completed (the
|
||||
seed is `user`; the newest message flips to `assistant` only once the model
|
||||
answered 鈥?an errored turn also persists an assistant message carrying the 鈿狅笍
|
||||
answered — an errored turn also persists an assistant message carrying the ⚠️
|
||||
usage-limit part, [org-internal #2912], so check the tail text before declaring success).
|
||||
Stays `user` across 2鈥? polls spaced 10鈥?0 s 鈫?**apparent silent stall**;
|
||||
Stays `user` across 2–3 polls spaced 10–30 s → **apparent silent stall**;
|
||||
almost certainly the model did not resolve to a usable one (fallback landed
|
||||
badly, or you omitted `model`). Redeliver Step 2 with an explicit known-good
|
||||
`model`. Need everything newer than a known point? Page forward with
|
||||
`after=<cursor>&limit=N` and follow the `X-Next-Cursor` response header 鈥?
|
||||
`after=<cursor>&limit=N` and follow the `X-Next-Cursor` response header —
|
||||
just never omit `limit`.
|
||||
|
||||
> 鈿狅笍 Do NOT write `/messages` (plural) 鈥?that path does not exist for listing.
|
||||
> ⚠️ Do NOT write `/messages` (plural) — that path does not exist for listing.
|
||||
> (`POST /session/:id/message`, same singular path but POST, is the synchronous
|
||||
> prompt endpoint; don't confuse the two.)
|
||||
|
||||
## Launch a session for a specific ticket (#N)
|
||||
|
||||
The common case: an agent (or cron/CI) spins up a fresh main session to work a
|
||||
tracked issue 鈥?seed prompt composed **from the issue body**, launched on the
|
||||
tracked issue — seed prompt composed **from the issue body**, launched on the
|
||||
current session's model. **Step-0 owner check is MANDATORY ([org-internal #1803])**: four data
|
||||
sources (session-title scan, assignee/claim, open PRs, remote branches); any
|
||||
live claim 鈫?ABORT the launch (`session-scope-guard.md` points here for that
|
||||
pre-step). Full recipe 鈥?owner-check scan script with `X-Total-Count` /
|
||||
live claim → ABORT the launch (`session-scope-guard.md` points here for that
|
||||
pre-step). Full recipe — owner-check scan script with `X-Total-Count` /
|
||||
`X-Has-More` pagination handling ([org-internal #3190]), claim-first atomic 3-step ([org-internal #2297],
|
||||
+ one-command provisioning via `script/claim-provision.sh`, [org-internal #3642]),
|
||||
seed-prompt MUSTs, backend duplicate-ticket hard guard ([org-internal #1989], on by default
|
||||
[org-internal #2350]), launch + poll commands: `reference/ticket-recipe.md` (read BEFORE
|
||||
creating any ticket-driven session).
|
||||
|
||||
## Completion-report protocol (瀹屾垚鍥炴姤鍗忚, [org-internal #2374])
|
||||
## Completion-report protocol (完成回报协议, [org-internal #2374])
|
||||
|
||||
`prompt_async` is fire-and-forget **for the launcher too**: without this
|
||||
protocol the orchestrator has NO push channel and must poll forever ([org-internal #2366]).
|
||||
The worker actively reports `status=done|blocked|handoff` back into the
|
||||
orchestrator's session via `prompt_async`, with an issue-comment fallback.
|
||||
Default report format is `branch=<ref>`-based 鈥?workers never open PRs
|
||||
Default report format is `branch=<ref>`-based — workers never open PRs
|
||||
(TD-678/[org-internal #4425]); `pr=#N` appears only on `uncoordinated` self-opens.
|
||||
Hard constraints: a headless worker MUST NOT call the `question` tool
|
||||
([org-internal #2378] 鈥?directional/irreversible decisions are `status=blocked` reports,
|
||||
([org-internal #2378] — directional/irreversible decisions are `status=blocked` reports,
|
||||
then STOP), and MUST NOT self-merge its PR / self-close the issue / touch
|
||||
`main` ([org-internal #2386] 鈥?keep the PR open; the orchestrator merges and closes). Full
|
||||
protocol 鈥?orchestrator-side seed block (verbatim template), worker-side
|
||||
`main` ([org-internal #2386] — keep the PR open; the orchestrator merges and closes). Full
|
||||
protocol — orchestrator-side seed block (verbatim template), worker-side
|
||||
steps, decision-authority layering: `reference/completion-report.md` (read
|
||||
BEFORE composing a seed prompt for any worker you need to hear back from).
|
||||
|
||||
## Post-launch lifecycle: health check, hung rescue, stale-worldview correction ([org-internal #2459])
|
||||
|
||||
Launching is half the job 鈥?a headless worker can hang silently or wake with
|
||||
a stale worldview. Health check = two polls 30鈥?0 s apart (`.state` + `progress`
|
||||
Launching is half the job — a headless worker can hang silently or wake with
|
||||
a stale worldview. Health check = two polls 30–60 s apart (`.state` + `progress`
|
||||
+ message count); `state=generating` with ALL counters frozen across BOTH
|
||||
samples = hung 鈫?abort FIRST (`POST /session/:id/abort`), then re-wake with a
|
||||
known-good model + the anti-re-hang clause. Stale worldview 鈫?fact-baseline
|
||||
samples = hung → abort FIRST (`POST /session/:id/abort`), then re-wake with a
|
||||
known-good model + the anti-re-hang clause. Stale worldview → fact-baseline
|
||||
injection (only the CURRENT authority injects; if YOU might be the stale one,
|
||||
verify identity against the durable record first). Full runbooks 鈥?verdict
|
||||
verify identity against the durable record first). Full runbooks — verdict
|
||||
table + [org-internal #3215] dual-sample rule, abort + re-wake sequence, fact-baseline
|
||||
structure, authorization asymmetry: `reference/lifecycle-ops.md` (read when
|
||||
managing a launched session). Quota wall 鈥?the worker's turn killed by a
|
||||
managing a launched session). Quota wall — the worker's turn killed by a
|
||||
terminal usage-limit error (looks like silent completion: no report, ticket
|
||||
stalled) 鈥?has its own runbook: detection signals (finish=error 鈿狅笍 tail,
|
||||
stalled) — has its own runbook: detection signals (finish=error ⚠️ tail,
|
||||
provider quota markers), provider-switch re-wake, fact-baseline template,
|
||||
pre-dispatch provider check: `reference/quota-wall-recovery.md` ([org-internal #3669], evidence
|
||||
[org-internal #3627]; read when a worker goes silent mid-task).
|
||||
|
||||
## 宸℃妯″紡 / Patrol mode (delayed self-wake, [org-internal #3937])
|
||||
## 巡检模式 / Patrol mode (delayed self-wake, [org-internal #3937])
|
||||
|
||||
A patrol loop periodically nudges a headless session without holding a
|
||||
connection or burning a turn on a blocking sleep. Since [org-internal #3937] A the server
|
||||
@@ -414,82 +414,82 @@ curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
|
||||
|
||||
Rules of the road:
|
||||
|
||||
- **202 + `delay_sec` 鈮?queued-behind-wedge 202.** Both are 202, but the
|
||||
- **202 + `delay_sec` ≠queued-behind-wedge 202.** Both are 202, but the
|
||||
delayed wake holds the prompt in memory and delivers it exactly once when
|
||||
the delay elapses (session must have been idle at intake; `0` equals
|
||||
omission 鈫?immediate 204 path). If the session was NOT idle at intake, the
|
||||
omission → immediate 204 path). If the session was NOT idle at intake, the
|
||||
intake keeps the queued semantics and `delay_sec` is ignored (logged
|
||||
server-side) 鈥?the same precedence as maintenance queue mode, where the
|
||||
server-side) — the same precedence as maintenance queue mode, where the
|
||||
intake is retained and `delay_sec` never delays the flush.
|
||||
- **Paused at wake 鈫?dropped, not stored.** The wake's `prompt()` rejects and
|
||||
- **Paused at wake → dropped, not stored.** The wake's `prompt()` rejects and
|
||||
the server publishes an error event; the message is never stored. A patrol
|
||||
driver should check session state before assuming delivery.
|
||||
- **Restart abandons pending wakes.** They are in-memory server fibers 鈥?no
|
||||
- **Restart abandons pending wakes.** They are in-memory server fibers — no
|
||||
persistence, no drain. After any server restart the patrol driver owns
|
||||
re-arming the next wake; treat a missed wake as expected, not as an error.
|
||||
- **Latest wake wins, per session ([org-internal #4069]).** Arming `delay_sec` on a session
|
||||
that already has a pending wake SUPERSEDES it: the previous countdown is
|
||||
cancelled, only the latest wake fires. Re-arming each turn is the intended
|
||||
patrol pattern 鈥?a superseded wake never delivers its prompt and publishes
|
||||
no error. (Pre-[org-internal #4069] stacks both FIFO 鈥?on old binaries, re-arm only after
|
||||
patrol pattern — a superseded wake never delivers its prompt and publishes
|
||||
no error. (Pre-[org-internal #4069] stacks both FIFO — on old binaries, re-arm only after
|
||||
the previous wake fired.)
|
||||
- **Never emulate a delay with the bash tool.** `sleep 900; curl ...` blocks
|
||||
the turn with zero output and gets manually aborted as a hang (the [org-internal #3937]
|
||||
incident). The bash tool now prepends a `<shell_warning>` on bare sleeps
|
||||
beyond 120s ([org-internal #3937] C) 鈥?treat that warning as a redirect to this section.
|
||||
beyond 120s ([org-internal #3937] C) — treat that warning as a redirect to this section.
|
||||
|
||||
## Do NOT use this skill when
|
||||
|
||||
- A **persistent client** (Web UI / interactive TUI) is attached 鈥?those use the
|
||||
- A **persistent client** (Web UI / interactive TUI) is attached — those use the
|
||||
synchronous `prompt` endpoint (it blocks until generation completes) and send
|
||||
`model` automatically. Using `prompt_async` there gains nothing and loses the
|
||||
in-line response. This includes the rescue runbook: a hung session driven by
|
||||
a persistent client is the client user's to handle (refresh / re-prompt from
|
||||
the UI) 鈥?abort + re-wake targets headless / managed sessions only.
|
||||
- You want the response **inline** in the same HTTP call 鈥?`prompt_async` is
|
||||
the UI) — abort + re-wake targets headless / managed sessions only.
|
||||
- You want the response **inline** in the same HTTP call — `prompt_async` is
|
||||
fire-and-forget; use the synchronous `prompt` endpoint instead.
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
| # | Pitfall | Fix |
|
||||
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | Omitting `model` / relying on the fallback 鈫?apparent silent stall (most common) | Always send a known-good `model:{providerID,modelID}` 鈥?reuse the current session's |
|
||||
| 2 | Listing endpoint written `/messages` (plural) 鈫?404 | Use `/session/:id/message` (singular); returns a bare array, role at `.info.role` |
|
||||
| 3 | Copying the session record's model verbatim into the payload | Remap field names: session `.model.id` 鈫?payload `modelID` (session uses `id`, payload uses `modelID`) |
|
||||
| 1 | Omitting `model` / relying on the fallback → apparent silent stall (most common) | Always send a known-good `model:{providerID,modelID}` — reuse the current session's |
|
||||
| 2 | Listing endpoint written `/messages` (plural) → 404 | Use `/session/:id/message` (singular); returns a bare array, role at `.info.role` |
|
||||
| 3 | Copying the session record's model verbatim into the payload | Remap field names: session `.model.id` → payload `modelID` (session uses `id`, payload uses `modelID`) |
|
||||
| 4 | Stale `octopus` binary rejects newer config schema (`Unrecognized key`) | Use the source CLI `bun run ./src/index.ts ...`; the config itself is valid |
|
||||
| 5 | Missing `?directory=` query param | In-session launch: default to the current session's directory. Cron/CI (no current session): pass the repo root explicitly 鈥?server falls back to its own `process.cwd()` |
|
||||
| 6 | Using synchronous `prompt` for headless launch | `prompt` blocks until done 鈥?wrong for fire-and-forget; use `prompt_async` |
|
||||
| 5 | Missing `?directory=` query param | In-session launch: default to the current session's directory. Cron/CI (no current session): pass the repo root explicitly — server falls back to its own `process.cwd()` |
|
||||
| 6 | Using synchronous `prompt` for headless launch | `prompt` blocks until done — wrong for fire-and-forget; use `prompt_async` |
|
||||
| 7 | Shell-escaping errors in a multi-line seed prompt | Build the payload with `jq -n --rawfile` from a file |
|
||||
| 8 | Verifying models via `GET /model` 鈥?no such endpoint (404) | Use `GET /provider` 鈫?`.connected` (provider live check); `list-models` tool also works. `GET /config/providers` lists _configured_ models only |
|
||||
| 9 | Launching a session for a ticket another live session already owns 鈫?conflicting diffs, duplicate work ([org-internal #1744]/[org-internal #1753]) | Run the Step-0 owner check (`GET /session` title scan + worktree/branch/PR scan); ABORT and report on conflict |
|
||||
| 10 | Creating a worktree / fresh checkout for the new session (unnecessary `bun install` + `.codegraph/` rebuild cost) | Don't 鈥?for _general_ launches: a session is a conversation pointed at a directory, so reuse the current workspace; worktree creation is a separate _workflow_ decision the launched session makes if/when it starts a workflow. **Ticket-recipe carve-out ([org-internal #2297]):** the ticket's work happens in a dedicated worktree anyway (worktree discipline), and claim-first pushes the workflow branch before launch 鈥?so the launcher pre-builds the worktree as the claim carrier + ready workspace, and the session reuses it (cost is front-loaded, not wasted). The worktree path goes in the claim comment + seed prompt; `?directory=` stays on the creator's workspace |
|
||||
| 11 | Launching a worker and never hearing back 鈥?orchestrator polls a few times, stops, loses visibility ([org-internal #2374]) | Append the completion-report block to the seed prompt (Completion-report protocol section): worker `prompt_async`s a one-line status back into the orchestrator session on done/blocked/handoff, with issue-comment fallback |
|
||||
| 12 | Headless worker calls the `question` tool 鈫?no attached client: the question deadlocks in a queue, or `auto_approve` silently auto-approves a direction-setting decision ([org-internal #2378]) | NEVER call `question` from a headless worker. Decision boundary (Completion-report protocol section): directional/irreversible decision 鈫?`status=blocked` report + one-line decision point, then STOP; the orchestrator is the sole human-decision entry point |
|
||||
| 13 | Headless worker self-merges its PR / self-closes the issue once the PR looks ready, despite a seed instruction to keep it open ([org-internal #2386]; N-02 [org-internal #2367] / N-03 [org-internal #2368], commits `d1565c99` / `040f21b7`) | Self-merge / self-close / touching `main` are irreversible directional actions 鈫?blocked by the Decision boundary. PR ready 鈫?keep it OPEN, report `status=done branch=<ref>`; the orchestrator opens/admits the PR, merges and closes uniformly |
|
||||
| 14 | Trusting `state=generating` as "alive" 鈥?a hung stream goes unrescued (session A, 2026-08-16: `generating` 6+ min, count frozen at 478) | Poll state AND message count twice 30鈥?0 s apart; frozen count = hung 鈫?abort + re-wake (Post-launch lifecycle section) |
|
||||
| 15 | Re-sending a wake prompt to a hung session and waiting 鈥?queued prompts are never consumed while the stream is dead | Abort FIRST (`POST /session/:id/abort` 鈫?200, verify `idle`), THEN re-send; a prompt queued before the abort may still never fire 鈥?always re-deliver |
|
||||
| 16 | A worker woken from restart / long idle acts on its stale worldview 鈥?re-dispatches superseded work or claims authority it no longer holds (2026-08-16: revived worker re-ran an already-published FAIL verify; ops session announced an inherited "orchestrator" takeover) | Fact-baseline injection (Post-launch lifecycle section): authoritative facts + invalidated assumptions + one concrete re-assignment. Only the CURRENT authority injects; a session that suspects IT is stale verifies identity against the durable record first (`rules/compact.md` identity clause) |
|
||||
| 17 | Worker goes silent mid-task: provider quota wall killed the turn (usage limit / 429) 鈥?no completion-report, no blocked report; from the issue side indistinguishable from silent completion (2026-08-29 [org-internal #3627]: opencode-go `weekly usage limit reached` after the 13:18 Round-1 FAIL; ticket stalled until manually re-woken) | Detect via tail probe (`finish=error` + 鈿狅笍 usage-limit part, `metadata.reason` in the quota family) + `GET /provider` quota markers; re-wake on a known-good provider with the fact-baseline template 鈥?`reference/quota-wall-recovery.md` ([org-internal #3669]). Prevent: pre-dispatch provider check + the seed's 閰嶉鑷姤 clause (`reference/completion-report.md`, [org-internal #3669]) 鈥?quota exhaustion is a `status=blocked reason=quota-exhausted` report, not a silent death |
|
||||
| 8 | Verifying models via `GET /model` — no such endpoint (404) | Use `GET /provider` → `.connected` (provider live check); `list-models` tool also works. `GET /config/providers` lists _configured_ models only |
|
||||
| 9 | Launching a session for a ticket another live session already owns → conflicting diffs, duplicate work ([org-internal #1744]/[org-internal #1753]) | Run the Step-0 owner check (`GET /session` title scan + worktree/branch/PR scan); ABORT and report on conflict |
|
||||
| 10 | Creating a worktree / fresh checkout for the new session (unnecessary `bun install` + `.codegraph/` rebuild cost) | Don't — for _general_ launches: a session is a conversation pointed at a directory, so reuse the current workspace; worktree creation is a separate _workflow_ decision the launched session makes if/when it starts a workflow. **Ticket-recipe carve-out ([org-internal #2297]):** the ticket's work happens in a dedicated worktree anyway (worktree discipline), and claim-first pushes the workflow branch before launch — so the launcher pre-builds the worktree as the claim carrier + ready workspace, and the session reuses it (cost is front-loaded, not wasted). The worktree path goes in the claim comment + seed prompt; `?directory=` stays on the creator's workspace |
|
||||
| 11 | Launching a worker and never hearing back — orchestrator polls a few times, stops, loses visibility ([org-internal #2374]) | Append the completion-report block to the seed prompt (Completion-report protocol section): worker `prompt_async`s a one-line status back into the orchestrator session on done/blocked/handoff, with issue-comment fallback |
|
||||
| 12 | Headless worker calls the `question` tool → no attached client: the question deadlocks in a queue, or `auto_approve` silently auto-approves a direction-setting decision ([org-internal #2378]) | NEVER call `question` from a headless worker. Decision boundary (Completion-report protocol section): directional/irreversible decision → `status=blocked` report + one-line decision point, then STOP; the orchestrator is the sole human-decision entry point |
|
||||
| 13 | Headless worker self-merges its PR / self-closes the issue once the PR looks ready, despite a seed instruction to keep it open ([org-internal #2386]; N-02 [org-internal #2367] / N-03 [org-internal #2368], commits `d1565c99` / `040f21b7`) | Self-merge / self-close / touching `main` are irreversible directional actions → blocked by the Decision boundary. PR ready → keep it OPEN, report `status=done branch=<ref>`; the orchestrator opens/admits the PR, merges and closes uniformly |
|
||||
| 14 | Trusting `state=generating` as "alive" — a hung stream goes unrescued (session A, 2026-08-16: `generating` 6+ min, count frozen at 478) | Poll state AND message count twice 30–60 s apart; frozen count = hung → abort + re-wake (Post-launch lifecycle section) |
|
||||
| 15 | Re-sending a wake prompt to a hung session and waiting — queued prompts are never consumed while the stream is dead | Abort FIRST (`POST /session/:id/abort` → 200, verify `idle`), THEN re-send; a prompt queued before the abort may still never fire — always re-deliver |
|
||||
| 16 | A worker woken from restart / long idle acts on its stale worldview — re-dispatches superseded work or claims authority it no longer holds (2026-08-16: revived worker re-ran an already-published FAIL verify; ops session announced an inherited "orchestrator" takeover) | Fact-baseline injection (Post-launch lifecycle section): authoritative facts + invalidated assumptions + one concrete re-assignment. Only the CURRENT authority injects; a session that suspects IT is stale verifies identity against the durable record first (`rules/compact.md` identity clause) |
|
||||
| 17 | Worker goes silent mid-task: provider quota wall killed the turn (usage limit / 429) — no completion-report, no blocked report; from the issue side indistinguishable from silent completion (2026-08-29 [org-internal #3627]: opencode-go `weekly usage limit reached` after the 13:18 Round-1 FAIL; ticket stalled until manually re-woken) | Detect via tail probe (`finish=error` + ⚠️ usage-limit part, `metadata.reason` in the quota family) + `GET /provider` quota markers; re-wake on a known-good provider with the fact-baseline template — `reference/quota-wall-recovery.md` ([org-internal #3669]). Prevent: pre-dispatch provider check + the seed's 配额自报 clause (`reference/completion-report.md`, [org-internal #3669]) — quota exhaustion is a `status=blocked reason=quota-exhausted` report, not a silent death |
|
||||
|
||||
## Known limitation (separate from this workaround)
|
||||
|
||||
A missing `model` producing an **apparent silent stall** (no surfaced error, no
|
||||
generation) is an observability / robustness gap. Current code does NOT
|
||||
hard-require `model` 鈥?it fills it via a fallback chain (`prompt.ts:858`) and any
|
||||
hard-require `model` — it fills it via a fallback chain (`prompt.ts:858`) and any
|
||||
resolution failure surfaces as a published `Session.Event.Error` on the
|
||||
synchronous path (`prompt.ts:804-822`), but the **async** `prompt_async` handler
|
||||
catches that cause (`forkPromptDelivery` in `handlers/session.ts`) so the HTTP
|
||||
caller never sees it. The proper fix is to surface the error (or reject unusable
|
||||
models at the boundary) so a stalled session is diagnosable. Until then, **always
|
||||
provide an explicit known-good `model`** 鈥?this skill is the documented
|
||||
workaround (see [org-internal #1695] "閬楃暀 minor").
|
||||
provide an explicit known-good `model`** — this skill is the documented
|
||||
workaround (see [org-internal #1695] "遗留 minor").
|
||||
|
||||
**Update ([org-internal #2912], 2026-08-20)**: usage-limit turn failures (HTTP 429 /
|
||||
FreeUsageLimitError / GoUsageLimitError) are no longer silent shells. When such
|
||||
a turn terminates (retry schedule ends or the spin is aborted), the errored
|
||||
assistant message persists `finish=error` plus a human-readable 鈿狅笍 text part
|
||||
assistant message persists `finish=error` plus a human-readable ⚠️ text part
|
||||
carrying provider, limit reason, and reset hint (`metadata: { error: true,
|
||||
reason, provider }`) 鈥?visible via `GET /session/:id/message` and rendered in
|
||||
reason, provider }`) — visible via `GET /session/:id/message` and rendered in
|
||||
the web UI like any assistant text. Pitfall #14's double-poll is still the
|
||||
liveness check while a 429 retry spin is IN progress (the spin itself retries
|
||||
with backoff until interrupted); the explicit error part only appears once the
|
||||
@@ -502,13 +502,13 @@ turn ends. The general async-path observability gap above remains tracked by
|
||||
- current-model reuse, and corrects the endpoint/claim against current code).
|
||||
- Completion-report protocol: **[org-internal #2374]** (worker-side active report back to the
|
||||
launching orchestrator session; fallback to issue comment).
|
||||
- Quota-wall recovery recipe: **[org-internal #3669]** (evidence instance [org-internal #3627]) 鈥?detect a
|
||||
- Quota-wall recovery recipe: **[org-internal #3669]** (evidence instance [org-internal #3627]) — detect a
|
||||
worker killed by a provider usage limit, re-wake on a known-good provider,
|
||||
pre-dispatch prevention.
|
||||
- Decision boundary: **[org-internal #2378]** (headless worker MUST NOT call `question`;
|
||||
directional decisions go through `status=blocked` reports to the
|
||||
orchestrator) and **[org-internal #2386]** (the worker MUST NOT self-merge its PR,
|
||||
self-close the issue, or touch `main` 鈥?the orchestrator merges and closes
|
||||
self-close the issue, or touch `main` — the orchestrator merges and closes
|
||||
uniformly).
|
||||
- Correction of the prior misdiagnosis: **[org-internal #1691]** (closed Invalid), comment 9205.
|
||||
- Synchronous client always sends `model`:
|
||||
@@ -519,29 +519,29 @@ turn ends. The general async-path observability gap above remains tracked by
|
||||
|
||||
## References
|
||||
|
||||
**On-demand references** (NOT injected 鈥?read at the declared timing):
|
||||
**On-demand references** (NOT injected — read at the declared timing):
|
||||
|
||||
- `reference/ticket-recipe.md` 鈥?ticket-driven launch (`Launch a session for a
|
||||
specific ticket (#N)`): read BEFORE creating any session for issue #N 鈥?
|
||||
- `reference/ticket-recipe.md` — ticket-driven launch (`Launch a session for a
|
||||
specific ticket (#N)`): read BEFORE creating any session for issue #N —
|
||||
Step-0 owner check, seed-prompt composition, launch + poll commands.
|
||||
- `reference/completion-report.md` 鈥?completion-report protocol (瀹屾垚鍥炴姤鍗忚,
|
||||
- `reference/completion-report.md` — completion-report protocol (完成回报协议,
|
||||
[org-internal #2374]): read BEFORE composing a seed prompt for a worker you need to hear
|
||||
back from 鈥?orchestrator block template, worker-side report steps, decision
|
||||
back from — orchestrator block template, worker-side report steps, decision
|
||||
boundary ([org-internal #2378] / [org-internal #2386]), quota self-report clause ([org-internal #3669]).
|
||||
- `reference/lifecycle-ops.md` 鈥?post-launch lifecycle ([org-internal #2459]): read when
|
||||
managing a launched session 鈥?health-check polling, hung-stream diagnosis
|
||||
- `reference/lifecycle-ops.md` — post-launch lifecycle ([org-internal #2459]): read when
|
||||
managing a launched session — health-check polling, hung-stream diagnosis
|
||||
(state=generating + frozen counters), abort + re-wake rescue,
|
||||
fact-baseline injection, authorization asymmetry.
|
||||
- `reference/quota-wall-recovery.md` 鈥?quota-wall recovery ([org-internal #3669], evidence
|
||||
- `reference/quota-wall-recovery.md` — quota-wall recovery ([org-internal #3669], evidence
|
||||
[org-internal #3627]): read when a worker goes silent mid-task (suspected provider usage
|
||||
limit) or BEFORE dispatching a long-running worker 鈥?detection signals
|
||||
(finish=error 鈿狅笍 tail, provider quota markers, log grep patterns),
|
||||
limit) or BEFORE dispatching a long-running worker — detection signals
|
||||
(finish=error ⚠️ tail, provider quota markers, log grep patterns),
|
||||
provider-switch re-wake + fact-baseline template, pre-dispatch provider
|
||||
check, quota self-report clause.
|
||||
|
||||
- `rules/headless-session-ops` wiki page 鈥?authoritative reference (this
|
||||
- `rules/headless-session-ops` wiki page — authoritative reference (this
|
||||
skill is its runtime carrier; link in the Directory-convention note above).
|
||||
- `core/rules/session-scope-guard.md` 鈥?owner-check + claim-first rule
|
||||
- `core/rules/session-scope-guard.md` — owner-check + claim-first rule
|
||||
(the ticket recipe's Step 0 is its launch-time application).
|
||||
- `core/rules/compact.md` 鈥?identity-verification clause backing the
|
||||
- `core/rules/compact.md` — identity-verification clause backing the
|
||||
authorization-asymmetry rule (`reference/lifecycle-ops.md`).
|
||||
|
||||
@@ -2,41 +2,41 @@
|
||||
name: project-kickoff
|
||||
description: >
|
||||
Use ONLY when the user asks about project setup, kickoff, or project
|
||||
initialization ("kickoff", "project setup", "椤圭洰鍒濆鍖?, "椤圭洰閰嶇疆").
|
||||
initialization ("kickoff", "project setup", "项目初始化", "项目配置").
|
||||
Provides guidance on the octopus project kickoff workflow: checking readiness,
|
||||
running /kickoff or octopus kickoff, configuring codegraph, and understanding
|
||||
project scaffolding. Do not use for application-level configuration 鈥?
|
||||
project scaffolding. Do not use for application-level configuration —
|
||||
this skill covers the octopus project lifecycle, not user application code.
|
||||
triggers:
|
||||
# High-discrimination phrases only ([org-internal #4525]): bare "setup"/"init"/"閰嶇疆" fire
|
||||
# High-discrimination phrases only ([org-internal #4525]): bare "setup"/"init"/"配置" fire
|
||||
# on any configuration mention, and the skill gate folds the previous
|
||||
# assistant turn on approval turns 鈥?one stray "閰嶇疆" in a reply re-fires
|
||||
# assistant turn on approval turns — one stray "配置" in a reply re-fires
|
||||
# the gate every turn. Keep triggers to phrases that name THIS workflow;
|
||||
# generic setup/init questions still route via the description in the
|
||||
# skills listing.
|
||||
- kickoff
|
||||
- project setup
|
||||
- 椤圭洰鍒濆鍖?
|
||||
- 椤圭洰閰嶇疆
|
||||
- 项目初始化
|
||||
- 项目配置
|
||||
role: Producer
|
||||
---
|
||||
|
||||
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛屽師 deferHard verbatimDir锛夈€傛満鍒躲€佺粨鏋勪笌 frontmatter 淇濇寔锛涘疄渚嬫湳璇紙宸ュ叿鍚嶃€佽矾寰勩€佸伐鍗曞彿锛夋寜 `core/adapters/TERMINOLOGY.md` 缁戝畾鍒板叿浣撳疄渚嬨€?
|
||||
> Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。
|
||||
# Project Kickoff
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Project kickoff is the octopus project initialization workflow that gets a project from zero to fully configured in one command. It covers readiness detection 鈫?setup 鈫?scaffold 鈫?config 鈫?codegraph init 鈫?skills overlay pull, producing a project that is ready for the full SDLC pipeline without manual configuration steps.
|
||||
Project kickoff is the octopus project initialization workflow that gets a project from zero to fully configured in one command. It covers readiness detection → setup → scaffold → config → codegraph init → skills overlay pull, producing a project that is ready for the full SDLC pipeline without manual configuration steps.
|
||||
|
||||
## 2. When This Skill Activates
|
||||
|
||||
This skill activates when the user's text matches any of the trigger keywords:
|
||||
|
||||
- `kickoff` 鈥?asking about the `/kickoff` command or the kickoff workflow
|
||||
- `project setup` 鈥?asking how to set up a project in octopus
|
||||
- `椤圭洰鍒濆鍖朻 / `椤圭洰閰嶇疆` 鈥?asking how to initialize or configure an octopus project (Chinese)
|
||||
- `kickoff` — asking about the `/kickoff` command or the kickoff workflow
|
||||
- `project setup` — asking how to set up a project in octopus
|
||||
- `项目初始化` / `项目配置` — asking how to initialize or configure an octopus project (Chinese)
|
||||
|
||||
Generic "setup"/"init"/"閰嶇疆" mentions are deliberately NOT triggers ([org-internal #4525]): they collide with everyday configuration talk, and the hard skill gate folds the previous assistant turn into its match text on approval turns 鈥?a passing "鎴栭厤缃?x: false" in a reply would re-fire the gate every turn. `/init` note: `/init` generates AGENTS.md via LLM; kickoff uses the managed template from `octopus setup` 鈥?the Builder distinguishes based on context.
|
||||
Generic "setup"/"init"/"配置" mentions are deliberately NOT triggers ([org-internal #4525]): they collide with everyday configuration talk, and the hard skill gate folds the previous assistant turn into its match text on approval turns — a passing "或配置x: false" in a reply would re-fire the gate every turn. `/init` note: `/init` generates AGENTS.md via LLM; kickoff uses the managed template from `octopus setup` — the Builder distinguishes based on context.
|
||||
|
||||
Typical user intents include: encountering a readiness prompt at session start, wondering how to initialize a project, or wanting to understand the kickoff workflow steps.
|
||||
|
||||
@@ -57,43 +57,43 @@ ls .gitea/workflows/ .github/workflows/ 2>/dev/null || echo "[GAP: no CI workflo
|
||||
```
|
||||
|
||||
If no workflows exist, recommend creating `.gitea/workflows/ci.yml` (this repo
|
||||
uses Gitea Actions) 鈥?or `.github/workflows/ci.yml` for a GitHub-hosted project
|
||||
鈥?with at minimum: typecheck, lint, and test gates. Without CI, every merge is a
|
||||
manual gate 鈥?type errors, lint regressions, and test failures can land on
|
||||
uses Gitea Actions) — or `.github/workflows/ci.yml` for a GitHub-hosted project
|
||||
— with at minimum: typecheck, lint, and test gates. Without CI, every merge is a
|
||||
manual gate — type errors, lint regressions, and test failures can land on
|
||||
main and go undetected until the next local checkout.
|
||||
|
||||
## 4. Workflow
|
||||
|
||||
The kickoff workflow runs in four automated steps:
|
||||
|
||||
1. **Check readiness** 鈥?Determine if the project needs setup. Run `checkReadinessWithGitea()` or `octopus kickoff --check-only` to inspect the current state.
|
||||
2. **Run kickoff** 鈥?Execute `/kickoff` from the TUI or `octopus kickoff` from the terminal.
|
||||
3. **What happens** 鈥?A Phase 0 gate, then five sequential phases:
|
||||
- Phase 0 鈥?**Gitea auth check**: derives the `gitea` block from the git remote origin (persisting missing fields), then verifies the token (`GET /user`) and runs the auth wizard on failure; never aborts kickoff
|
||||
- [1/5] **Setup** 鈥?Creates project directories and writes `AGENTS.md` from the managed template
|
||||
- [2/5] **Scaffold** 鈥?Creates the `<instance-root>/` skeleton (`core/skills/` directory + default `octopus.jsonc` template if absent); domain skills content arrives via the [5/5] overlay pull
|
||||
- [3/5] **Config stub + MCP wiring** 鈥?Writes the `mcp.codegraph` block into `octopus.jsonc`
|
||||
- [4/5] **Codegraph init** 鈥?Initializes and builds the per-project `.codegraph/` index
|
||||
- [5/5] **Skills remote + overlay pull** 鈥?Opt-in semantics ([org-internal #4370]); branches by flags when no `overlay.remotes` entry is configured:
|
||||
- no flags (interactive run) 鈥?**safe default**: nothing is written; a remediation hint is printed (pass `--skills-remote`, or edit `overlay.remotes`), and the readiness skills item stays not-ready
|
||||
- `--yes` 鈥?legacy behavior kept: writes the default skills remote, then pulls domain skills content (`octopus skills pull` equivalent)
|
||||
- `--skills-remote <owner/repo>` (+ optional `--skills-remote-base-url <url>`) 鈥?writes the specified remote instead (ref `main`; base URL defaults to `<instance-base-url>`), then pulls from it
|
||||
- `overlay.remotes` already configured 鈥?the write is skipped (idempotent; an explicit `--skills-remote` is ignored with a warning)
|
||||
4. **Post-kickoff verification** 鈥?Run `octopus kickoff --check-only` again. After a `--yes` or `--skills-remote` run it reports all 5 items as ready; after a no-flag interactive run it reports 4 of 5 鈥?the skills item stays not-ready until a remote is configured (`--skills-remote`, `--yes`, or a manual `overlay.remotes` edit + `octopus skills pull`).
|
||||
1. **Check readiness** — Determine if the project needs setup. Run `checkReadinessWithGitea()` or `octopus kickoff --check-only` to inspect the current state.
|
||||
2. **Run kickoff** — Execute `/kickoff` from the TUI or `octopus kickoff` from the terminal.
|
||||
3. **What happens** — A Phase 0 gate, then five sequential phases:
|
||||
- Phase 0 — **Gitea auth check**: derives the `gitea` block from the git remote origin (persisting missing fields), then verifies the token (`GET /user`) and runs the auth wizard on failure; never aborts kickoff
|
||||
- [1/5] **Setup** — Creates project directories and writes `AGENTS.md` from the managed template
|
||||
- [2/5] **Scaffold** — Creates the `<instance-root>/` skeleton (`core/skills/` directory + default `octopus.jsonc` template if absent); domain skills content arrives via the [5/5] overlay pull
|
||||
- [3/5] **Config stub + MCP wiring** — Writes the `mcp.codegraph` block into `octopus.jsonc`
|
||||
- [4/5] **Codegraph init** — Initializes and builds the per-project `.codegraph/` index
|
||||
- [5/5] **Skills remote + overlay pull** — Opt-in semantics ([org-internal #4370]); branches by flags when no `overlay.remotes` entry is configured:
|
||||
- no flags (interactive run) — **safe default**: nothing is written; a remediation hint is printed (pass `--skills-remote`, or edit `overlay.remotes`), and the readiness skills item stays not-ready
|
||||
- `--yes` — legacy behavior kept: writes the default skills remote, then pulls domain skills content (`octopus skills pull` equivalent)
|
||||
- `--skills-remote <owner/repo>` (+ optional `--skills-remote-base-url <url>`) — writes the specified remote instead (ref `main`; base URL defaults to `<instance-base-url>`), then pulls from it
|
||||
- `overlay.remotes` already configured — the write is skipped (idempotent; an explicit `--skills-remote` is ignored with a warning)
|
||||
4. **Post-kickoff verification** — Run `octopus kickoff --check-only` again. After a `--yes` or `--skills-remote` run it reports all 5 items as ready; after a no-flag interactive run it reports 4 of 5 — the skills item stays not-ready until a remote is configured (`--skills-remote`, `--yes`, or a manual `overlay.remotes` edit + `octopus skills pull`).
|
||||
|
||||
### First-ticket hint ([org-internal #3061])
|
||||
|
||||
For a brand-new project still validating its concept, open the FIRST ticket as
|
||||
`Kind/MVP` 鈥?the interactive no-pipeline mode (decision log + debt register in
|
||||
`Kind/MVP` — the interactive no-pipeline mode (decision log + debt register in
|
||||
the ticket body). It graduates by relabeling to `Kind/Feature` (analyze-dag
|
||||
backfill mode picks up the decision log) when a second session, an external
|
||||
contract, or a second consumer of a shared type appears. Do not route early
|
||||
concept work into the full pipeline "to be safe" 鈥?the pipeline's coordination
|
||||
concept work into the full pipeline "to be safe" — the pipeline's coordination
|
||||
machinery is cost, not quality, at zero coordination.
|
||||
|
||||
## 5. Re-kickoff / Idempotency
|
||||
|
||||
Re-running kickoff on an already-configured project is safe 鈥?every step is idempotent:
|
||||
Re-running kickoff on an already-configured project is safe — every step is idempotent:
|
||||
|
||||
- **Setup** skips existing directories
|
||||
- **Scaffold** skips existing files
|
||||
@@ -107,26 +107,26 @@ Re-running kickoff on an already-configured project is safe 鈥?every step is id
|
||||
|
||||
Common issues and their resolutions:
|
||||
|
||||
- **`codegraph: command not found`** 鈥?Run the `codegraph-setup` skill, or install via `bun add -g @colbymchenry/codegraph`
|
||||
- **Permission denied** 鈥?Check project directory ownership and write permissions
|
||||
- **Partially complete state** 鈥?Re-run `/kickoff`; idempotent steps will complete the missing parts without affecting already-completed steps
|
||||
- **MCP tools not available** 鈥?Verify `mcp.codegraph` exists in `octopus.jsonc`; re-run `octopus kickoff` to write it
|
||||
- **`codegraph: command not found`** — Run the `codegraph-setup` skill, or install via `bun add -g @colbymchenry/codegraph`
|
||||
- **Permission denied** — Check project directory ownership and write permissions
|
||||
- **Partially complete state** — Re-run `/kickoff`; idempotent steps will complete the missing parts without affecting already-completed steps
|
||||
- **MCP tools not available** — Verify `mcp.codegraph` exists in `octopus.jsonc`; re-run `octopus kickoff` to write it
|
||||
|
||||
## 7. Related Commands
|
||||
|
||||
- `/init` 鈥?Generates `AGENTS.md` using an LLM (custom, context-aware); kickoff uses the managed template from `octopus setup`
|
||||
- `octopus setup` 鈥?Creates directories + `AGENTS.md` from the managed template; kickoff calls this internally
|
||||
- `octopus scaffold` 鈥?Creates the `<instance-root>/` skeleton (`core/skills/` directory + default `octopus.jsonc` template if absent); run `octopus skills pull` to fetch domain skills content; kickoff calls this internally
|
||||
- `octopus kickoff --check-only` 鈥?Readiness check only, no pipeline execution
|
||||
- `octopus kickoff --skills-remote <owner/repo>` 鈥?Explicit opt-in: writes the given skills remote to `overlay.remotes` (ref `main`) and pulls it instead of the default
|
||||
- `octopus kickoff --skills-remote-base-url <url>` 鈥?Base URL for `--skills-remote` (requires it); defaults to `<instance-base-url>` when omitted
|
||||
- `octopus kickoff --yes` 鈥?Skips the opt-in confirmation and keeps the legacy behavior of writing the default skills remote (`Octopus/octopus-skills`)
|
||||
- `codegraph-setup` skill 鈥?For installing the codegraph CLI and initializing the per-project index
|
||||
- `/init` — Generates `AGENTS.md` using an LLM (custom, context-aware); kickoff uses the managed template from `octopus setup`
|
||||
- `octopus setup` — Creates directories + `AGENTS.md` from the managed template; kickoff calls this internally
|
||||
- `octopus scaffold` — Creates the `<instance-root>/` skeleton (`core/skills/` directory + default `octopus.jsonc` template if absent); run `octopus skills pull` to fetch domain skills content; kickoff calls this internally
|
||||
- `octopus kickoff --check-only` — Readiness check only, no pipeline execution
|
||||
- `octopus kickoff --skills-remote <owner/repo>` — Explicit opt-in: writes the given skills remote to `overlay.remotes` (ref `main`) and pulls it instead of the default
|
||||
- `octopus kickoff --skills-remote-base-url <url>` — Base URL for `--skills-remote` (requires it); defaults to `<instance-base-url>` when omitted
|
||||
- `octopus kickoff --yes` — Skips the opt-in confirmation and keeps the legacy behavior of writing the default skills remote (`Octopus/octopus-skills`)
|
||||
- `codegraph-setup` skill — For installing the codegraph CLI and initializing the per-project index
|
||||
|
||||
## 8. Reference
|
||||
|
||||
- `<harness-package>/src/project-kickoff/readiness.ts` 鈥?`checkReadiness()` implementation (sync 4-item base)
|
||||
- `<harness-package>/src/project-kickoff/readiness-gitea.ts` 鈥?`checkReadinessWithGitea()` composed 5-item check (adds gitea auth reachable)
|
||||
- `<harness-package>/src/cli/cmd/kickoff.ts` 鈥?`/kickoff` command handler
|
||||
- `core/skills/codegraph-setup/SKILL.md` 鈥?CodeGraph setup skill
|
||||
- Project roadmap at `.artifacts/project-kickoff/` (deprecated 鈥?now stored as wiki page `project-kickoff/roadmap/` via `wiki 璇诲啓 API锛堣 TERMINOLOGY锛塦)
|
||||
- `<harness-package>/src/project-kickoff/readiness.ts` — `checkReadiness()` implementation (sync 4-item base)
|
||||
- `<harness-package>/src/project-kickoff/readiness-gitea.ts` — `checkReadinessWithGitea()` composed 5-item check (adds gitea auth reachable)
|
||||
- `<harness-package>/src/cli/cmd/kickoff.ts` — `/kickoff` command handler
|
||||
- `core/skills/codegraph-setup/SKILL.md` — CodeGraph setup skill
|
||||
- Project roadmap at `.artifacts/project-kickoff/` (deprecated — now stored as wiki page `project-kickoff/roadmap/` via `wiki 读写 API(见 TERMINOLOGY)`)
|
||||
|
||||
@@ -4,22 +4,22 @@ description: >
|
||||
Use ONLY when ready to cut a release. The Release Manager (Worker) inspects
|
||||
project state (git log, version files, build) to determine the semver bump,
|
||||
generate the changelog, tag, and produce a release artifact. Works on any
|
||||
project state 鈥?no pipeline artifacts required.
|
||||
project state — no pipeline artifacts required.
|
||||
triggers:
|
||||
- release
|
||||
- cut release
|
||||
- 鍙戠増
|
||||
- 发版
|
||||
- publish release
|
||||
- ship release
|
||||
role: Producer
|
||||
---
|
||||
|
||||
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛屽師 deferHard verbatimDir锛夈€傛満鍒躲€佺粨鏋勪笌 frontmatter 淇濇寔锛涘疄渚嬫湳璇紙宸ュ叿鍚嶃€佽矾寰勩€佸伐鍗曞彿锛夋寜 `core/adapters/TERMINOLOGY.md` 缁戝畾鍒板叿浣撳疄渚嬨€?
|
||||
> Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。
|
||||
# Release
|
||||
|
||||
Prepare and tag a release based on project facts 鈥?git history since the last
|
||||
Prepare and tag a release based on project facts — git history since the last
|
||||
tag, current version, build status, and dependency health. No dependency on `.artifacts/{slug}/`
|
||||
(**deprecated** 鈥?SDLC artifacts now live in the Gitea wiki at `{slug}/...`;
|
||||
(**deprecated** — SDLC artifacts now live in the Gitea wiki at `{slug}/...`;
|
||||
see `_shared/gitea-read-patterns.md`) or any pipeline skill outputs.
|
||||
|
||||
**This SKILL does not deploy.** Deployment is project-specific (K8s, Docker,
|
||||
@@ -32,12 +32,12 @@ The release is owned and executed by the **Release Manager** (Worker).
|
||||
|
||||
**Context compaction**: release is a pipeline stage boundary. The main session
|
||||
compacts at this clean boundary ONLY when a capacity/projection trigger holds,
|
||||
per `core/rules/compact.md` 搂"Stage-boundary compaction" (long multi-stage
|
||||
runs 鈥?DAG Epic orchestration 鈥?keep the legacy every-boundary compaction;
|
||||
short runs 鈥?bugfix / DAG task 鈥?and standalone runs default to NOT
|
||||
per `core/rules/compact.md` §"Stage-boundary compaction" (long multi-stage
|
||||
runs — DAG Epic orchestration — keep the legacy every-boundary compaction;
|
||||
short runs — bugfix / DAG task — and standalone runs default to NOT
|
||||
compacting). The Release Manager itself is single-phase and
|
||||
artifact-driven (version files, changelog, tag); a mid-run compaction loses
|
||||
nothing 鈥?re-read the release checklist state and git log to resume.
|
||||
nothing — re-read the release checklist state and git log to resume.
|
||||
|
||||
---
|
||||
|
||||
@@ -50,7 +50,7 @@ Before starting the release:
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 鈥?Pre-release Gate
|
||||
## Phase 1 — Pre-release Gate
|
||||
|
||||
Run each check against the live project. Stop and report failures.
|
||||
|
||||
@@ -65,82 +65,82 @@ Run each check against the live project. Stop and report failures.
|
||||
- No new HIGH or CRITICAL CVEs block the release.
|
||||
- Pre-existing HIGH/CRITICAL CVEs do NOT block but **MUST be filed as
|
||||
tech-debt** before the release lands. File them in TWO TIERS (batching
|
||||
rule, [org-internal #3846] 鈥?a single release audit once fanned out to 14 TDs 鈫?14 PRs 鈫?
|
||||
rule, [org-internal #3846] — a single release audit once fanned out to 14 TDs → 14 PRs →
|
||||
56-84 CI runs on a saturated runner pool): Tier A below is the general
|
||||
mechanical-change batching rule; Tier B and the escape hatch follow it.
|
||||
|
||||
<a id="phase-1-tier-a"></a>
|
||||
### Tier A 鈥?閫氱敤鏈烘鍙樻洿骞跺崟瑙勫垯 / Universal mechanical-change batching rule
|
||||
### Tier A — 通用机械变更并单规则 / Universal mechanical-change batching rule
|
||||
|
||||
Origin [org-internal #3846] (audit-batch pilot, generalized in place): the rule covers ANY
|
||||
single-source fan-out of mechanical changes, not just dependency audits.
|
||||
Two source types today: (a) dependency bump audits; (b) docs 鎵归噺淇
|
||||
Two source types today: (a) dependency bump audits; (b) docs 批量修订
|
||||
(batch docs revisions).
|
||||
|
||||
#### 瑙勫垯姝f枃 / Rule text
|
||||
#### 规则正文 / Rule text
|
||||
|
||||
- **Core**: mechanical changes of ONE class fanning out from a single
|
||||
source are filed as ONE batch issue + ONE batch PR 鈥?never N脳issue +
|
||||
N脳PR. The batch body keeps one row per item (per-CVE / per-doc mapping),
|
||||
source are filed as ONE batch issue + ONE batch PR — never N×issue +
|
||||
N×PR. The batch body keeps one row per item (per-CVE / per-doc mapping),
|
||||
so per-item traceability is unchanged; work the batch as a single PR.
|
||||
- **(a) Dependency bump audits** (the [org-internal #3846] origin case): advisories whose
|
||||
remediation is a plain version bump (lockfile-only diff, no semver-major
|
||||
jump, no overrides/catalog surgery, no API or adapter fallout) are filed
|
||||
together via `宸ュ崟 API锛堣 TERMINOLOGY锛塩reate` with
|
||||
together via `工单 API(见 TERMINOLOGY)create` with
|
||||
`title="[{origin}] audit-batch@{version}: mechanical bumps for {N} advisories (TD-{NNN})"`
|
||||
and labels `[tech_debt_label_id, severity_label_id, kind_bug_label_id]`
|
||||
(severity = highest in the batch). One row per advisory 鈥?CVE/GHSA ID,
|
||||
affected package + from鈫抰o version, severity (CVSS), advisory URL,
|
||||
(severity = highest in the batch). One row per advisory — CVE/GHSA ID,
|
||||
affected package + from→to version, severity (CVSS), advisory URL,
|
||||
recommended remediation, and a per-advisory Reactivation Trigger
|
||||
("resolved when `npm audit` / `bun audit` reports no HIGH/CRITICAL for
|
||||
this advisory") 鈥?so the release-notes `TD-NNN 鈫?#NNNN` mapping stays
|
||||
this advisory") — so the release-notes `TD-NNN → #NNNN` mapping stays
|
||||
per-advisory.
|
||||
- **(b) Docs 鎵归噺淇 (batch docs revisions)**: many small same-class docs
|
||||
- **(b) Docs 批量修订 (batch docs revisions)**: many small same-class docs
|
||||
corrections discovered in one pass (e.g. a terminology sweep) file as ONE
|
||||
issue via the same flow,
|
||||
`title="[{origin}] docs-batch: {class} revisions for {N} files"`, with one
|
||||
row per file (path, correction, reason); same ONE-batch-PR landing.
|
||||
- **Landing**: when members live on separate branches, compose the single
|
||||
batch PR via the `land-batch` skill (`core/skills/land-batch/SKILL.md`
|
||||
鈥?cross-branch batch composition, topology B). For dependency-bump
|
||||
— cross-branch batch composition, topology B). For dependency-bump
|
||||
batches pass `batch-compose --convergent bun.lock` (script
|
||||
`.gitea/scripts/batch-compose.ts`): the regenerable lockfile is exempt
|
||||
from path-overlap admission and lockfile-only conflicts are surgically
|
||||
resolved to the running head's version.
|
||||
|
||||
#### 閫傜敤杈圭晫 / Applicability boundary
|
||||
#### 适用边界 / Applicability boundary
|
||||
|
||||
- **闈?mechanical锛堝惈鍒ゆ柇鎴愬垎鐨勫彉鏇达紝涓嶅苟鍗曪級** 鈥?items requiring judgment
|
||||
- **非mechanical(含判断成分的变更,不并单)** — items requiring judgment
|
||||
(wording decisions, behavior/API changes, review-dependent edits) are
|
||||
never batched; file each as its own issue.
|
||||
- **璺緞閲嶅彔锛坆atch 鎴愬憳瑙︾鐩稿悓鏂囦欢 鈫?涓嶅苟鍗曪紱鍞竴璞佸厤 =
|
||||
鍙啀鐢熷叡浜枃浠?`bun.lock` 缁?batch-compose `--convergent` 鏀舵暃锛?* 鈥?the sole
|
||||
- **路径重叠(batch 成员触碰相同文件 → 不并单;唯一豁免 =
|
||||
可再生共享文件`bun.lock` 经batch-compose `--convergent` 收敛)** — the sole
|
||||
path-overlap exemption is the regenerable shared lockfile under
|
||||
`--convergent`; every other same-file collision stays un-batched.
|
||||
- **semver-major 璺冲彉 / overrides路catalog 鎵嬫湳 / adapter路peer 鑱斿姩锛堚啋
|
||||
Tier B 鐙珛鍗曪紝缁存寔 per-package锛?* 鈥?these stay per-package Tier B
|
||||
- **semver-major 跳变 / overrides·catalog 手术 / adapter·peer 联动(→
|
||||
Tier B 独立单,维持 per-package)** — these stay per-package Tier B
|
||||
issues. Tier B and the escape hatch (below) survive this generalization
|
||||
unchanged.
|
||||
|
||||
#### 闄嶇骇璺緞 / Degradation path
|
||||
#### 降级路径 / Degradation path
|
||||
|
||||
- **骞跺崟 PR CI 澶辫触 鈫?鎸夐攣鏂囦欢 hunk 鎷嗗寘鍥為€€锛坆isect锛?*: for dependency
|
||||
- **并单 PR CI 失败 → 按锁文件 hunk 拆包回退(bisect)**: for dependency
|
||||
batches, bisect by splitting lockfile hunks per package back into
|
||||
per-package PRs 鈥?the original Tier A bisect semantics carried over
|
||||
verbatim (鍚岃涔夛紝娉涘寲鎺緸: every mechanical batch degrades the same way).
|
||||
per-package PRs — the original Tier A bisect semantics carried over
|
||||
verbatim (同语义,泛化措辞: every mechanical batch degrades the same way).
|
||||
- **Docs batches**: split per file back into per-file PRs.
|
||||
- **land-batch composition fallback** (exit codes per
|
||||
`.gitea/scripts/batch-compose.ts`): `3` path-overlap 鈫?split the batch
|
||||
`.gitea/scripts/batch-compose.ts`): `3` path-overlap → split the batch
|
||||
along the reported pairs (or land the overlapping member 1:1) and re-run
|
||||
per group; `4` merge conflict / `5` transport/git error 鈫?fall back to
|
||||
per group; `4` merge conflict / `5` transport/git error → fall back to
|
||||
1:1 PRs for the whole batch.
|
||||
- **Preflight hedge ([org-internal #3846])**: run `bun install --dry-run` before opening
|
||||
a dependency-batch PR 鈥?the resolver accepting the composed version set
|
||||
a dependency-batch PR — the resolver accepting the composed version set
|
||||
is a cheap pre-CI rejection of impossible bump combinations.
|
||||
|
||||
Tier B and the escape hatch (unchanged by the generalization):
|
||||
|
||||
- **Tier B 鈥?surgery, one issue per package**: semver-major jumps,
|
||||
- **Tier B — surgery, one issue per package**: semver-major jumps,
|
||||
adapter/peer fallout, or overrides/catalog surgery keep the
|
||||
per-package issue: `title="[{origin}] {CVE-ID/GHSA-ID} in {package} (TD-{NNN})"`
|
||||
with the same labels; body requirements match the Tier A rows.
|
||||
@@ -148,7 +148,7 @@ Tier B and the escape hatch (unchanged by the generalization):
|
||||
issue when same-day remediation is required (urgent HIGH/CRITICAL).
|
||||
|
||||
De-duplicate against open `tech-debt` issues (match by
|
||||
CVE/GHSA ID) before creating. Record the `TD-NNN 鈫?#NNNN` mapping in the
|
||||
CVE/GHSA ID) before creating. Record the `TD-NNN → #NNNN` mapping in the
|
||||
release notes. See `verify` Phase 5.5 for the tech-debt promotion body
|
||||
template and `_shared/gitea-write-patterns.md` Pattern 3.
|
||||
|
||||
@@ -157,19 +157,19 @@ template and `_shared/gitea-write-patterns.md` Pattern 3.
|
||||
|
||||
| Check | Status |
|
||||
| ----------------- | ----------------------------------------------------- |
|
||||
| Clean workspace | 鉁?/ 鉂? |
|
||||
| Clean workspace | ✅/ ❌ |
|
||||
| Branch | {branch name} |
|
||||
| Build | 鉁?/ 鉂? |
|
||||
| Typecheck + Lint | 鉁?/ 鉂? |
|
||||
| Tests | 鉁?/ 鉂?(N passed, M failed) |
|
||||
| Dependency audit | 鉁?/ 鈿狅笍 N known CVEs (pre-existing) / 鉂?N new CVEs |
|
||||
| Build | ✅/ ❌ |
|
||||
| Typecheck + Lint | ✅/ ❌ |
|
||||
| Tests | ✅/ ❌(N passed, M failed) |
|
||||
| Dependency audit | ✅/ ⚠️ N known CVEs (pre-existing) / ❌N new CVEs |
|
||||
```
|
||||
|
||||
If any gate fails except known CVEs, stop and report what failed.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 鈥?Version Bump
|
||||
## Phase 2 — Version Bump
|
||||
|
||||
Determine the new version by inspecting git history since the last tag.
|
||||
|
||||
@@ -193,14 +193,14 @@ Determine the new version by inspecting git history since the last tag.
|
||||
| `docs:` / `chore:` | (skip) | Not user-visible |
|
||||
|
||||
If no conventional prefix found, infer from the subject line:
|
||||
- "add", "implement", "introduce" 鈫?MINOR
|
||||
- "fix", "resolve", "correct" 鈫?PATCH
|
||||
- "remove", "drop", "rename" (public API) 鈫?MAJOR
|
||||
- "add", "implement", "introduce" → MINOR
|
||||
- "fix", "resolve", "correct" → PATCH
|
||||
- "remove", "drop", "rename" (public API) → MAJOR
|
||||
|
||||
5. **Compute bump**:
|
||||
- If any MAJOR commit 鈫?bump MAJOR.
|
||||
- Else if any MINOR commit 鈫?bump MINOR.
|
||||
- Else 鈫?bump PATCH.
|
||||
- If any MAJOR commit → bump MAJOR.
|
||||
- Else if any MINOR commit → bump MINOR.
|
||||
- Else → bump PATCH.
|
||||
|
||||
```markdown
|
||||
## Version Bump
|
||||
@@ -210,12 +210,12 @@ Determine the new version by inspecting git history since the last tag.
|
||||
**New version**: {new version}
|
||||
**Type**: MAJOR / MINOR / PATCH
|
||||
**Commits since last tag**: {N}
|
||||
**Reason**: {justification 鈥?e.g. "2 feat + 3 fix 鈫?MINOR"}
|
||||
**Reason**: {justification — e.g. "2 feat + 3 fix → MINOR"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 鈥?Changelog
|
||||
## Phase 3 — Changelog
|
||||
|
||||
Generate the changelog from `git log <last-tag>..HEAD`:
|
||||
|
||||
@@ -224,10 +224,10 @@ Generate the changelog from `git log <last-tag>..HEAD`:
|
||||
- Scope (if present, e.g. `feat(auth):`).
|
||||
- Description (the subject line, past tense, human-readable).
|
||||
2. **Group by type**:
|
||||
- **Added** 鈥?`feat:` commits.
|
||||
- **Changed** 鈥?modifications to existing behavior (non-breaking).
|
||||
- **Fixed** 鈥?`fix:` commits.
|
||||
- **Breaking** 鈥?`BREAKING CHANGE:` or `!:` commits.
|
||||
- **Added** — `feat:` commits.
|
||||
- **Changed** — modifications to existing behavior (non-breaking).
|
||||
- **Fixed** — `fix:` commits.
|
||||
- **Breaking** — `BREAKING CHANGE:` or `!:` commits.
|
||||
3. **Deduplicate**: squash multiple commits for the same change into one entry
|
||||
where it makes narrative sense.
|
||||
4. Read the existing root `CHANGELOG.md` (if any) and prepend this release.
|
||||
@@ -235,7 +235,7 @@ Generate the changelog from `git log <last-tag>..HEAD`:
|
||||
Write to root `CHANGELOG.md` (prepend section).
|
||||
|
||||
```markdown
|
||||
## [{version}] 鈥?{YYYY-MM-DD}
|
||||
## [{version}] — {YYYY-MM-DD}
|
||||
|
||||
### Added
|
||||
|
||||
@@ -251,24 +251,24 @@ Write to root `CHANGELOG.md` (prepend section).
|
||||
|
||||
### Breaking
|
||||
|
||||
- {breaking change} ({hash}) 鈥?see migration notes above
|
||||
- {breaking change} ({hash}) — see migration notes above
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 鈥?Tag & Finalize
|
||||
## Phase 4 — Tag & Finalize
|
||||
|
||||
1. **Update version file** 鈥?write the new version to the project's version
|
||||
1. **Update version file** — write the new version to the project's version
|
||||
manifest.
|
||||
2. **Commit**: `git add` version file + changelog file, commit with message:
|
||||
```
|
||||
release: {version}
|
||||
```
|
||||
3. **Tag**: `git tag v{version}` (adjust prefix per project convention 鈥?
|
||||
3. **Tag**: `git tag v{version}` (adjust prefix per project convention —
|
||||
check existing tags with `git tag -l`).
|
||||
4. **Verify tag**: `git tag -l v{version}` confirms the tag exists.
|
||||
|
||||
**Do not push** 鈥?the user must explicitly request pushing to remote.
|
||||
**Do not push** — the user must explicitly request pushing to remote.
|
||||
|
||||
```markdown
|
||||
## Release Artifact
|
||||
@@ -282,15 +282,15 @@ Write to root `CHANGELOG.md` (prepend section).
|
||||
|
||||
| File | Change |
|
||||
| -------------- | ------------------------- |
|
||||
| {version file} | {old} 鈫?{new} |
|
||||
| {version file} | {old} → {new} |
|
||||
| CHANGELOG.md | Prepended {version} |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4b 鈥?Publish Release Artifacts (octopus project, manual)
|
||||
## Phase 4b — Publish Release Artifacts (octopus project, manual)
|
||||
|
||||
> Octopus-specific. Other projects: substitute your own artifact pipeline 鈥?
|
||||
> Octopus-specific. Other projects: substitute your own artifact pipeline —
|
||||
> the goal is identical (turn the tag into downloadable assets).
|
||||
|
||||
The CI publish pipeline (`.gitea/workflows/publish.yml`) was **retired by
|
||||
@@ -303,17 +303,17 @@ credentials in the environment.
|
||||
|
||||
- Clean checkout of the release commit (tag pushed or about to be pushed).
|
||||
- Credentials in env:
|
||||
- `GITEA_TOKEN` 鈥?PAT with `write:repository` (release create/undraft,
|
||||
- `GITEA_TOKEN` — PAT with `write:repository` (release create/undraft,
|
||||
tag push) and `write:package` (container registry). The `CI_PAT` secret
|
||||
value is the canonical token.
|
||||
- `NODE_AUTH_TOKEN` 鈥?only if publishing to npm.
|
||||
- `AUR_KEY` 鈥?only if pushing the AUR package.
|
||||
- `NODE_AUTH_TOKEN` — only if publishing to npm.
|
||||
- `AUR_KEY` — only if pushing the AUR package.
|
||||
- Docker logged in to the Gitea container registry:
|
||||
`echo "$GITEA_TOKEN" | docker login <instance-registry-host> -u <user> --password-stdin`.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Version + draft release** (idempotent 鈥?skips if already published):
|
||||
1. **Version + draft release** (idempotent — skips if already published):
|
||||
```bash
|
||||
GITEA_TOKEN=<pat> GH_REPO=Octopus/octopus bun script/version.ts
|
||||
```
|
||||
@@ -344,7 +344,7 @@ credentials in the environment.
|
||||
-H "Content-Type: application/json" -d '{"draft": false}'
|
||||
```
|
||||
|
||||
5. **Verify assets** 鈥?the release must carry 12 assets:
|
||||
5. **Verify assets** — the release must carry 12 assets:
|
||||
```bash
|
||||
curl -sS "<instance-base-url>/api/v1/repos/Octopus/octopus/releases/tags/v{version}" \
|
||||
-H "Authorization: token $GITEA_TOKEN" | jq '.assets | length'
|
||||
@@ -368,7 +368,7 @@ credentials in the environment.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 鈥?Rollback Plan
|
||||
## Phase 5 — Rollback Plan
|
||||
|
||||
Document how to undo this release:
|
||||
|
||||
@@ -379,7 +379,7 @@ Document how to undo this release:
|
||||
2. **Data rollback** (if applicable):
|
||||
- For each migration, confirm the `down` migration exists and has been tested.
|
||||
- If the release changes data format without a reversible migration, mark
|
||||
`鈿狅笍 IRREVERSIBLE DATA CHANGE`.
|
||||
`⚠️ IRREVERSIBLE DATA CHANGE`.
|
||||
|
||||
```markdown
|
||||
## Rollback Plan
|
||||
@@ -403,7 +403,7 @@ git tag -d v{version}
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 鈥?Post-release Smoke Test
|
||||
## Phase 6 — Post-release Smoke Test
|
||||
|
||||
1. Run the project's build command on the tagged commit. Must pass.
|
||||
2. Run the test suite. Must pass.
|
||||
@@ -411,15 +411,15 @@ git tag -d v{version}
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 鈥?Report
|
||||
## Phase 7 — Report
|
||||
|
||||
```markdown
|
||||
## Release Report
|
||||
|
||||
**Version**: {old} 鈫?{new} (MAJOR / MINOR / PATCH)
|
||||
**Version**: {old} → {new} (MAJOR / MINOR / PATCH)
|
||||
**Tag**: v{version}
|
||||
**Commits**: {N} since last tag ({feat} features, {fix} fixes)
|
||||
**Typecheck**: 鉁?| Lint: 鉁?| Tests: 鉁?
|
||||
**Typecheck**: ✅| Lint: ✅| Tests: ✅
|
||||
|
||||
### Changelog
|
||||
|
||||
@@ -427,21 +427,21 @@ git tag -d v{version}
|
||||
|
||||
### Release Artifacts
|
||||
|
||||
- Commit: {hash} 鈥?`release: {version}`
|
||||
- Commit: {hash} — `release: {version}`
|
||||
- Tag: v{version}
|
||||
- Changelog: CHANGELOG.md updated
|
||||
|
||||
---
|
||||
|
||||
**Deploy**: {manual step 鈥?"merge to main triggers CI", etc.}
|
||||
**Deploy**: {manual step — "merge to main triggers CI", etc.}
|
||||
**Rollback**: see Phase 5 above
|
||||
**Next**: `core/skills/retrospective/SKILL.md` (optional 鈥?run on any project state)
|
||||
**Next**: `core/skills/retrospective/SKILL.md` (optional — run on any project state)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- `core/checklists/release.md` 鈥?Release checklist
|
||||
- `core/checklists/release.md` — Release checklist
|
||||
- Semver spec: https://semver.org
|
||||
- Conventional Commits: https://www.conventionalcommits.org
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, "..")
|
||||
const CORE_DIR = path.join(ROOT, "core")
|
||||
|
||||
const SCAN_EXTENSIONS = new Set([".md", ".json", ".ts", ".tsx", ".js", ".mjs", ".cjs", ".yaml", ".yml", ".txt"])
|
||||
|
||||
export const MOJIBAKE_SIGNATURES: { name: string; pattern: RegExp }[] = [
|
||||
{ name: "em-dash-mojibake", pattern: /\u9225/ },
|
||||
{ name: "right-arrow-mojibake", pattern: /\u922B/ },
|
||||
{ name: "check-mark-mojibake", pattern: /\u9257|\u9253/ },
|
||||
{ name: "cross-mark-mojibake", pattern: /\u9242/ },
|
||||
{ name: "geq-mojibake", pattern: /\u922E/ },
|
||||
{ name: "multiplication-mojibake", pattern: /\u8137/ },
|
||||
{ name: "replacement-char", pattern: /\uFFFD/ },
|
||||
{ name: "truncated-mojibake", pattern: /[\u9000-\u9FFF]\?/ },
|
||||
{ name: "gbk-cjk-bigram", pattern: /椤圭|鍒濆|閰嶇|鐨勶|鎷夎|浼氳瘽|鏃犲|淇濇|缁戝|瀹屾|涓珛|鏀瑰啓|瀹炰緥|鎵归|寤惰|璋冨|绀轰|鍘熸|閰嶉|鎹薄|閫昏|椤甸|绔犵|宸插|鍒嗛|鍏抽|鍚岀|鍚庣|鍓嶇|杩斿|杩涘|閫変|鎸夌|鏉冮|鏂囦|鏂囨/ },
|
||||
]
|
||||
|
||||
export interface EncodingViolation {
|
||||
file: string
|
||||
line: number
|
||||
signature: string
|
||||
match: string
|
||||
}
|
||||
|
||||
export function scanFile(filePath: string): EncodingViolation[] {
|
||||
const text = fs.readFileSync(filePath, "utf8")
|
||||
const violations: EncodingViolation[] = []
|
||||
const lines = text.split(/\r?\n/)
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
for (const sig of MOJIBAKE_SIGNATURES) {
|
||||
const m = lines[i]!.match(sig.pattern)
|
||||
if (m) {
|
||||
violations.push({
|
||||
file: filePath,
|
||||
line: i + 1,
|
||||
signature: sig.name,
|
||||
match: m[0]!,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
export function collectFiles(dir: string): string[] {
|
||||
const out: string[] = []
|
||||
if (!fs.existsSync(dir)) return out
|
||||
const walk = (d: string) => {
|
||||
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
||||
const full = path.join(d, entry.name)
|
||||
if (entry.isDirectory()) walk(full)
|
||||
else if (SCAN_EXTENSIONS.has(path.extname(entry.name))) out.push(full)
|
||||
}
|
||||
}
|
||||
walk(dir)
|
||||
return out.sort()
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
if (!fs.existsSync(CORE_DIR)) {
|
||||
console.error(`check-encoding-health: core/ directory not found at ${CORE_DIR} — cannot verify (fail-closed)`)
|
||||
process.exit(1)
|
||||
}
|
||||
const files = collectFiles(CORE_DIR)
|
||||
if (files.length === 0) {
|
||||
console.error(`check-encoding-health: 0 scannable files under ${CORE_DIR} — cannot verify (fail-closed)`)
|
||||
process.exit(1)
|
||||
}
|
||||
const violations: EncodingViolation[] = []
|
||||
for (const f of files) {
|
||||
try {
|
||||
violations.push(...scanFile(f))
|
||||
} catch (err) {
|
||||
console.error(`check-encoding-health: failed to read ${f}: ${(err as Error).message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
if (violations.length > 0) {
|
||||
console.error(`check-encoding-health: ${violations.length} mojibake violation(s) in core/:`)
|
||||
for (const v of violations) {
|
||||
const rel = path.relative(ROOT, v.file)
|
||||
console.error(` ${rel}:${v.line} [${v.signature}] ${JSON.stringify(v.match)}`)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`check-encoding-health: OK (${files.length} files scanned, 0 violations)`)
|
||||
}
|
||||
|
||||
if (import.meta.main) main()
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "octopus-workflow",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"description": "Standalone workflow core: provider-neutral agent workflow corpus (skills / rules / checklists / templates / schemas) with drift guards, extracted from the octopus agent platform.",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
@@ -9,7 +9,8 @@
|
||||
"scripts": {
|
||||
"check:core-cohesion": "bun guards/check-core-cohesion.ts",
|
||||
"check:core-p1": "bun guards/check-core-p1.ts",
|
||||
"delink:core": "bun guards/delink-core.ts"
|
||||
"delink:core": "bun guards/delink-core.ts",
|
||||
"check:encoding-health": "bun guards/check-encoding-health.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user