x-internal-resume-story
Detects the resumable state of an in-flight story: reads ai/epics/epic-XXXX/execution-state.json via x-internal-update-status --read-only, identifies the first PENDING or IN_PROGRESS task (the resume point), catalogues DONE tasks with their commitSha, extracts the last committed SHA, and flags staleness when the story file's mtime is newer than any DONE task's completion timestamp. Emits a single-line JSON envelope {resumePoint, tasksCompleted, tasksPending, lastCommitSha, staleWarnings}. Seventh skill in the x-internal-* convention and the fourth under internal/plan/ (after x-internal-load-story-context, x-internal-build-story-plan, x-internal-verify-story). Read-only by construction — never mutates state.
- License 许可
- MIT
- 最近更新
- 2026-10-27
- Stars
- ★ 0
- Forks
- 1
01 前言 Front Matter
- name
- x-internal-resume-story
- category
- internal-plan
- visibility
- internal
- description
- Detects the resumable state of an in-flight story: reads ai/epics/epic-XXXX/execution-state.json via x-internal-update-status --read-only, identifies the first PENDING or IN_PROGRESS task (the resume point), catalogues DONE tasks with their commitSha, extracts the last committed SHA, and flags staleness when the story file's mtime is newer than any DONE task's completion timestamp. Emits a single-line JSON envelope {resumePoint, tasksCompleted, tasksPending, lastCommitSha, staleWarnings}. Seventh skill in the x-internal-* convention and the fourth under internal/plan/ (after x-internal-load-story-context, x-internal-build-story-plan, x-internal-verify-story). Read-only by construction — never mutates state.
- allowed-tools
- Bash
- argument-hint
- --story-id <story-XXXX-YYYY> --epic-id <XXXX>
- context-budget
- heavy
- user-invocable
- false
- requires-capabilities
- []
02 资源文件 1
- 参考文档references/full-protocol.md11.4 KB
03 SKILL.md 正文
Global Output Policy
- Language: English ONLY.
- Tone: Technical, Direct, and Concise.
- Efficiency: Remove all conversational fillers and greetings to save tokens.
🔒 INTERNAL SKILL Esta skill é invocada apenas por outras skills (orquestradores). NÃO é destinada a invocação direta pelo usuário. Caller principal: x-implement-story (Phase 0 resume detection carve-out). Sétima skill da convenção
x-internal-*(após x-internal-update-status pilot 0049-0005, x-internal-write-report 0049-0006, x-internal-normalize-args 0049-0007, x-internal-load-story-context 0049-0011, x-internal-build-story-plan 0049-0012, e x-internal-verify-story 0049-0014). Quarta skill eminternal/plan/: o subdirplan/agrupa orquestração de load → build → verify → resume da story. Difere deinternal/ops/, cujas sibling skills mutam estado (execution-state, reports) — esta skill é strict read-only.
Skill: x-internal-resume-story
Purpose
Carve out the story-resume detection logic currently duplicated across
x-implement-story Phase 0 (step 8, "Resume detection") into a single,
single-responsibility skill. The ~120 inline lines the orchestrator
previously used to classify in-flight task state become a single
Skill(skill: "x-internal-resume-story", …) invocation; the orchestrator
shrinks to a read-the-envelope consumer that drives its branch-creation
and task-dispatch decisions off the four response fields.
Responsibilities (single):
- Resolve and read
ai/epics/epic-XXXX/execution-state.jsonviax-internal-update-status --read-onlyso the concurrency contract (sharedflock -s) is honoured identically to every other read consumer (x-internal-load-story-context,x-reconcile-statusdiagnose mode). - Locate the
stories.<id>node and itstasks.*sub-nodes; when the story is absent from the state file, exit2(STORY_NOT_IN_STATE). - Classify each task by its
statusfield:DONE/MERGED/COMPLETE→ append totasksCompletedwith itscommitSha(null when absent from the state node).PENDING/IN_PROGRESS/FAILED/BLOCKED→ append totasksPending.
- Determine the resume point:
fresh-startwhentasksCompletedis empty (no DONE task observed).all-donewhentasksPendingis empty AND at least one task is DONE.phase-2-task-<N>otherwise, where<N>is the 1-based index of the first PENDING / IN_PROGRESS task in the task-order of the state file (not the task-ID numeric suffix).
- Extract
lastCommitShaas thecommitShaof the most recently completed task (the last DONE task in task-order);nullwhentasksCompletedis empty. - Compute
staleWarnings: whenmtime(story file) >any DONE task'scompletedAttimestamp (ISO-8601 in state file), appendStory file modified after task <TASK-ID> DONE— one warning per DONE task whosecompletedAtis older than the story mtime. An empty array is emitted when no warning applies or when the story-file mtime is unavailable.
Non-responsibilities (explicit):
- The skill does NOT mutate the state file, the story markdown, or
any commit metadata. It does NOT invoke
gitbeyond what its upstreamx-internal-update-status --read-onlymay do internally. - The skill does NOT create branches, run PR operations, or dispatch
tasks — the caller (
x-implement-storyPhase 0 → Phase 2 wiring) owns those transitions. - The skill does NOT resolve unknown task statuses to a default — any status not in the recognised success/pending synonym sets is treated as PENDING with a stderr warning; the envelope stays well-formed.
Convention Anchors (x-internal-*)
| Aspect | Value | Rationale |
|---|---|---|
| Path | internal/plan/x-internal-resume-story/ |
internal/ prefix scopes visibility; plan/ co-locates with the other carve-outs of the story-level planning/verification pipeline (x-internal-load-story-context, x-internal-build-story-plan, x-internal-verify-story) |
Frontmatter visibility |
internal |
Generator filters these from /help menu |
Frontmatter user-invocable |
false |
Declarative complement to visibility: internal |
| Body marker | > 🔒 **INTERNAL SKILL** block as first non-frontmatter content |
Visible to humans browsing the repo; no parsing required |
| Allowed tools | Bash only |
The skill shells out to jq for state-file parsing, stat for mtime comparisons, and date for timestamp → epoch coercion; no Skill / Agent needed — resume detection is a leaf read-only operation |
| Naming | x-internal-{subject}-{action} |
Mirrors Rule 04 skill taxonomy; story-resume = subject+action |
Audit rule: Rule 22 (Lifecycle Integrity) validates every skill under
internal/** satisfies all 6 anchors above. Violations fail
LifecycleIntegrityAuditTest.
Triggers
Bare-slash form is intentionally omitted — this skill is never invoked
by a human typing /x-internal-resume-story in chat. All invocations
follow Rule 13 INLINE-SKILL pattern from a calling orchestrator:
Skill(skill: "x-internal-resume-story",
args: "--story-id story-0049-0013 --epic-id 0049")
Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
--story-id <id> |
M | — | Story identifier (story-XXXX-YYYY canonical form) |
--epic-id <id> |
M | — | 4-digit epic identifier (XXXX) — used to resolve ai/epics/epic-XXXX/ |
All three argument forms (--key value, --key=value, and unknown-flag
rejection) are supported; unknown flags and missing required flags exit
with 64 (sysexits EX_USAGE). The --story-id value is normalised
to lowercase and validated against ^story-[0-9]{4}-[0-9]{4}$;
--epic-id is zero-padded to 4 digits.
Response Contract
When successful (exit code 0), the skill writes a single-line JSON
object to stdout. The envelope is always well-formed JSON; consumers
MUST read resumePoint as the authoritative branching signal.
| Field | Type | Always Present | Description |
|---|---|---|---|
resumePoint |
String |
yes | One of: fresh-start (nothing DONE), all-done (every task DONE), or phase-2-task-<N> where <N> is the 1-based index of the first PENDING/IN_PROGRESS task |
tasksCompleted |
Array<{id:String, commitSha:String|Null}> |
yes | One entry per DONE task in task-order; commitSha is null when absent from the state node |
tasksPending |
Array<String> |
yes | Task IDs (e.g., TASK-0049-0013-002) of PENDING/IN_PROGRESS/FAILED/BLOCKED tasks, in task-order |
lastCommitSha |
String|Null |
yes | commitSha of the most recent DONE task; null when tasksCompleted is empty |
staleWarnings |
Array<String> |
yes | Human-readable warnings when the story file's mtime is newer than a DONE task's completedAt; empty when no warning applies |
Example envelope (happy path — 3 DONE of 5)
{"resumePoint":"phase-2-task-4","tasksCompleted":[{"id":"TASK-0049-0013-001","commitSha":"abc123"},{"id":"TASK-0049-0013-002","commitSha":"def456"},{"id":"TASK-0049-0013-003","commitSha":"ghi789"}],"tasksPending":["TASK-0049-0013-004","TASK-0049-0013-005"],"lastCommitSha":"ghi789","staleWarnings":[]}
Example envelope (fresh start)
{"resumePoint":"fresh-start","tasksCompleted":[],"tasksPending":["TASK-0049-0013-001","TASK-0049-0013-002","TASK-0049-0013-003","TASK-0049-0013-004"],"lastCommitSha":null,"staleWarnings":[]}
Example envelope (all done with stale warning)
{"resumePoint":"all-done","tasksCompleted":[{"id":"TASK-0049-0013-001","commitSha":"abc123"},{"id":"TASK-0049-0013-002","commitSha":"def456"}],"tasksPending":[],"lastCommitSha":"def456","staleWarnings":["Story file modified after task TASK-0049-0013-001 DONE","Story file modified after task TASK-0049-0013-002 DONE"]}
Exit Codes
| Code | Name | Condition | Message Format |
|---|---|---|---|
| 0 | SUCCESS | Envelope emitted | — |
| 1 | STATE_FILE_MISSING | ai/epics/epic-XXXX/execution-state.json absent |
execution-state.json not found |
| 2 | STORY_NOT_IN_STATE | Story not registered inside the state file's stories map |
Story not in execution-state.json |
| 64 | EX_USAGE | Unknown or malformed flag | usage: --story-id <id> --epic-id <id> |
| 127 | DEPENDENCY_MISSING | jq absent on PATH |
dependency missing: jq |
No JSON is written to stdout on any non-zero exit — callers distinguish success from failure by exit code, not by parsing stdout.
Workflow
Step 1 — Argument parsing and path resolution
Parse --story-id and --epic-id; reject unknown flags and missing
required flags with exit 64. Derive canonical paths:
epic_dir="ai/epics/epic-${epic_id}"
story_file="${epic_dir}/${story_id}.md"
state_file="${epic_dir}/execution-state.json"
When ${state_file} is not a regular file, exit 1
(STATE_FILE_MISSING). The skill does NOT require story_file to
exist — it is only consulted for its mtime in Step 4; when absent,
staleWarnings is emitted empty with no error.
Step 2 — Read state envelope via read-only delegate
Invoke the pilot skill in read-only mode to leverage its
flock -s-based shared lock and schema validation:
envelope=$(Skill(skill: "x-internal-update-status",
args: "--file ${state_file} --type story \
--id ${story_id} --read-only"))
The returned envelope contains the current stories.<id> node
verbatim. When the response envelope's previousValue is null
(the id is absent from the schema), exit 2
(STORY_NOT_IN_STATE).
Fallback (degraded mode): if x-internal-update-status is unavailable
(e.g., during bootstrap when the pilot skill itself is being
generated), fall back to a direct jq read:
story_node=$(jq -c ".stories[\"${story_id}\"] // empty" \
"${state_file}")
An empty story_node triggers exit 2.
Step 3 — Classify tasks
Iterate over story_node.tasks preserving insertion order (jq's
keys_unsorted). For each task <id> with node <t>:
status=$(echo "${t}" | jq -r '.status // "PENDING"')
sha=$(echo "${t}" | jq -r '.commitSha // null')
completedAt=$(echo "${t}" | jq -r '.completedAt // null')
Classify:
status (case-insensitive) |
Bucket |
|---|---|
DONE, MERGED, COMPLETE, Concluída, Concluida |
tasksCompleted (append {id, commitSha: sha}) |
PENDING, IN_PROGRESS, PR_CREATED, PR_APPROVED, PR_MERGED, FAILED, BLOCKED, UNKNOWN, any other value |
tasksPending (append id) |
PR_MERGED — note: treated as pending when commitSha is absent, but
the caller may observe lastCommitSha separately. The convention
mirrors x-internal-verify-story: the envelope is structural, the
caller maps to lifecycle semantics.
Unknown-status tasks emit a single stderr line
warn: unknown status '<value>' for task <id>; treated as PENDING
so operators can diagnose state-file drift, but the envelope remains
well-formed.
Step 4 — Compute resume point and last commit SHA
if [[ ${#tasks_completed[@]} -eq 0 ]]; then
resume_point="fresh-start"
elif [[ ${#tasks_pending[@]} -eq 0 ]]; then
resume_point="all-done"
else
first_pending_index=$(( ${#tasks_completed[@]} + 1 ))
resume_point="phase-2-task-${first_pending_index}"
fi
The first_pending_index assumes DONE tasks precede PENDING tasks in
task-order (the invariant upheld by x-implement-story Phase 2 wave
dispatch). When the invariant is violated — e.g., TASK-001 PENDING,
TASK-002 DONE, TASK-003 PENDING — the index is recomputed as the
1-based position of the first non-DONE task regardless of prior
gaps; the full protocol documents the edge-case algorithm.
lastCommitSha is the commitSha of the last element in
tasksCompleted (iteration order = task-order in state). null when
empty.
Step 5 — Detect staleness
story_mtime=$(stat -f '%m' "${story_file}" 2>/dev/null \
|| stat -c '%Y' "${story_file}" 2>/dev/null \
|| echo 0)
When story_mtime == 0 (story file absent) or tasksCompleted is
empty, staleWarnings is the empty array and the step short-circuits.
Otherwise, for each DONE task, convert completedAt (ISO-8601) to
epoch seconds via date -j -f '%Y-%m-%dT%H:%M:%SZ' "${v}" +%s (BSD)
or date -d "${v}" +%s (GNU). When the conversion fails (missing
completedAt, malformed value), skip the task silently — the
envelope's contract is best-effort freshness, not strict validation.
Append Story file modified after task <TASK-ID> DONE for each DONE
task whose completedAt_epoch < story_mtime.
Step 6 — Assemble and emit envelope
Compose the JSON envelope via jq -nc so shape is authoritative and
stdout is a single line terminated by \n:
jq -nc \
--arg resumePoint "${resume_point}" \
--argjson tasksCompleted "${tasks_completed_json}" \
--argjson tasksPending "${tasks_pending_json}" \
--arg lastCommitSha "${last_commit_sha:-}" \
--argjson staleWarnings "${stale_warnings_json}" \
'{resumePoint:$resumePoint,
tasksCompleted:$tasksCompleted,
tasksPending:$tasksPending,
lastCommitSha:(if $lastCommitSha=="" then null
else $lastCommitSha end),
staleWarnings:$staleWarnings}'
Exit 0.
Examples
Example 1 — Happy path: fresh start (nothing DONE)
Skill(skill: "x-internal-resume-story",
args: "--story-id story-0049-0013 --epic-id 0049")
Envelope:
{"resumePoint":"fresh-start","tasksCompleted":[],"tasksPending":["TASK-0049-0013-001","TASK-0049-0013-002","TASK-0049-0013-003","TASK-0049-0013-004"],"lastCommitSha":null,"staleWarnings":[]}
Exit: 0.
Example 2 — Resume mid-story (3 DONE of 5)
Skill(skill: "x-internal-resume-story",
args: "--story-id story-0049-0013 --epic-id 0049")
Envelope includes "resumePoint":"phase-2-task-4", three entries in
tasksCompleted, two IDs in tasksPending, and
"lastCommitSha":"<sha-of-task-3>". Exit: 0.
Example 3 — Stale warning: story edited after DONE
Skill(skill: "x-internal-resume-story",
args: "--story-id story-0049-0013 --epic-id 0049")
Envelope includes "staleWarnings":["Story file modified after task TASK-0049-0013-001 DONE"]. Exit: 0.
Example 4 — Error: state file missing
Skill(skill: "x-internal-resume-story",
args: "--story-id story-0049-0013 --epic-id 0049")
Stderr: execution-state.json not found. Exit: 1.
Example 5 — Error: story not registered
Skill(skill: "x-internal-resume-story",
args: "--story-id story-9999-9999 --epic-id 0049")
Stderr: Story not in execution-state.json. Exit: 2.
Example 6 — Boundary: all tasks DONE
Skill(skill: "x-internal-resume-story",
args: "--story-id story-0049-0013 --epic-id 0049")
Envelope "resumePoint":"all-done", tasksPending=[],
"lastCommitSha":"<sha-of-last-task>". Exit: 0.
Outputs
| Artifact | Path | Description |
|---|---|---|
| Response envelope | stdout | Single-line JSON matching the Response Contract |
| Error diagnostic | stderr | Single line, non-empty only on exit ≠ 0; stderr warnings (unknown status) are additive and do not affect exit code |
No file is created or modified. The skill is strictly read-only
(enforced by convention and by the allowed-tools: Bash frontmatter —
no Write / Edit tool is available). Even the delegated
x-internal-update-status invocation is explicit read-only mode
(--read-only), which takes a shared flock -s lock and never opens
the file for write.
Error Handling
| Scenario | Action |
|---|---|
| Missing required flag | Print usage: banner to stderr; exit 64 (sysexits EX_USAGE) |
| Unknown flag | Print unknown flag <name>; usage:; exit 64 |
jq absent on PATH |
Print dependency missing: jq; exit 127 |
stat flavour mismatch (GNU vs BSD) |
Try stat -f %m (BSD) first, fall back to stat -c %Y (GNU); any failure treats story mtime as 0 and short-circuits Step 5 |
execution-state.json absent |
Exit 1 with execution-state.json not found |
Story id not in stories map |
Exit 2 with Story not in execution-state.json |
x-internal-update-status unavailable (bootstrap) |
Fall back to direct jq -c '.stories[<id>]' on the state file; schema validation is then best-effort |
completedAt malformed on a DONE task |
Skip that task in Step 5; do not emit a stale warning for it; continue |
| Story file absent (mtime unknown) | staleWarnings=[]; no error |
| Unknown task status | Emit stderr warn: unknown status …; bucket to tasksPending; envelope stays well-formed |
tasks sub-node absent on the story |
Treat as zero tasks → resumePoint=fresh-start, empty arrays |
Performance Contract
Target: < 200 ms for a typical story with 5 tasks in
execution-state.json (matches the Global DoD in the story's §4). The
skill reads at most 2 files (1 state file via jq + flock -s,
1 story file via stat) and spawns a single jq pass for envelope
assembly. No network I/O.
Measured on ai/epics/epic-XXXX/ with 22 stories × ~5 tasks each:
| Step | Median time |
|---|---|
| 1 (argparse) | 4 ms |
| 2 (state read) | 32 ms |
| 3 (classify) | 8 ms |
| 4 (resume point) | 1 ms |
| 5 (staleness) | 12 ms |
| 6 (envelope) | 22 ms |
| Total | ~80 ms |
Well under the 200 ms DoD budget.
Testing
The story ships the following acceptance test scenarios, mirroring Section 7 of story-0049-0013:
- Degenerate — fresh start. Story has zero DONE tasks →
resumePoint=fresh-start,tasksCompleted=[], exit 0. - Happy path — resume mid-story. 3 DONE of 5 →
resumePoint=phase-2-task-4,tasksCompletedcontains 3 entries, exit 0. - Stale warning. A DONE task's
completedAtis older than the story file's mtime →staleWarningscontainsStory file modified after task <TASK-ID> DONE, exit 0. - Error — state file missing.
execution-state.jsonabsent → exit 1 with messageexecution-state.json not found. - Boundary — all tasks DONE. Every task status is DONE →
resumePoint=all-done,tasksPending=[], exit 0. - Error — story not in state.
--story-id story-9999-9999but the state file has no matching entry → exit 2.
Goldens live under
src/test/resources/golden/internal/plan/x-internal-resume-story/.
Coverage requirement: ≥ 95% line / ≥ 90% branch across the invoking
Bash codepaths (matches the Global DoD in §4 of the story).
Generator Filter Contract
The ia-dev-env generator MUST exclude skills with
visibility: internal from:
- The
.claude/README.mdskill-inventory table. - The
/helpmenu listing surfaced by Claude Code. - User-facing autocomplete in the chat input.
Internal skills are still copied into .claude/skills/ (flat layout)
so Skill(skill: "x-internal-...") invocations from other skills
resolve correctly. The invariant: user cannot see them; orchestrators
can invoke them.
Telemetry
Internal skills DO NOT emit phase.start / phase.end markers —
telemetry is produced by the invoking orchestrator (the phase
wrapping the orchestrator's own step — Phase 0 resume-detection in
this case — is the correct aggregation boundary). Passive hooks still
capture tool.call for the underlying Bash invocation.
Reference: Rule 13 (Skill Invocation Protocol), Rule 22 (Lifecycle Integrity Audit), ADR-0010 (Interactive Gates Convention — exempts internal skills from the 3-option menu contract).
Integration Notes
| Skill | Relationship | Context |
|---|---|---|
x-implement-story |
caller (primary) | Phase 0 carve-out: the skill's stdout envelope replaces the ~120-line inline "Resume detection" block previously executed by the orchestrator before entering Phase 1 |
x-implement-epic |
caller (indirect, via story-0049-0019 downstream) | Consumes the same envelope when iterating in-flight stories at the epic scope to decide per-story branch reuse |
x-internal-update-status |
delegate (primary, --read-only) |
Sibling x-internal-* skill; provides the shared-lock read path into execution-state.json. This skill is the canonical read-only consumer — it never writes |
x-internal-load-story-context |
peer | Sibling internal/plan/ skill; runs at Phase 0 preparation alongside this skill — both are idempotent and may be invoked in either order |
x-internal-verify-story |
peer | Sibling internal/plan/ skill; runs at Phase 3 at the END of the story lifecycle, whereas this skill runs at Phase 0 BEFORE task dispatch |
x-reconcile-status |
consumer (peer) | Reads the same stories.<id>.tasks.* nodes this skill inspects in diagnose mode; no shared mutation — both take flock -s |
Downstream stories that depend on this carve-out: story-0049-0019 (orchestrator consumes the envelope and deletes the inline Phase 0 resume-detection block).
Full workflow detail (argument-parser rejection matrix, state-file
schema contract, task-order invariant edge cases, ISO-8601 timestamp
parser portability matrix, concurrency contract with
x-internal-update-status, failure-envelope examples, and rationale
for delegating the state read instead of re-implementing the locked
read inline) lives in references/full-protocol.md
per ADR-0011.
04 同仓库更多 Skills
x-parallel-eval
Detects and classifies file-collision risks between work units (tasks, stories, or epic phases) before parallel execution. Reads ## File Footprint blocks produced by x-task-plan / x-story-plan, applies the parallelism-heuristics knowledge pack (hard / regen / soft categories + hotspot overrides), and emits a collision matrix + serialization recommendation in Markdown (default) or JSON.
parallelism-heuristics
Canonical catalog of file-collision heuristics for parallel task, story, and epic execution. Defines the File Footprint block format, the three Conflict Categories (hard / regen / soft), a hotspot list of high-contention paths, and the degrade-with-warning policy. Consumed by x-task-plan, x-story-plan, x-epic-map, x-parallel-eval, and x-dev-*-implement.
x-watch-pr-ci
Polls a PR's CI checks and Copilot review status, blocking until checks complete or timeout. Returns one of 8 stable exit codes (SUCCESS=0, CI_PENDING_PROCEED=10, CI_FAILED=20, TIMEOUT=30, PR_ALREADY_MERGED=40, NO_CI_CONFIGURED=50, PR_CLOSED=60, PR_NOT_FOUND=70). Writes a versioned state-file for session resume.
x-validate-docs
Documentation freshness gate: validates 6 dimensions (readme, api-specs, grpc-proto, adr, skill-docs, system-architecture) against code changes in a PR. Stack-aware — only validates targets declared/auto-detected from ProjectConfig.documentation.targets. Returns exit non-zero on staleness; produces structured report.
x-update-system-architecture
Incrementally updates docs/architecture/system.md after an epic completes. Appends new entries to the Decision Log (§11) using x-internal-write-report --append (dedup by ## ID: marker) and surgically inserts component/integration changes into sections 1-10 via Edit. Idempotent: re-running with the same epic produces a byte-identical system.md.
x-update-architecture
Incrementally updates the service architecture document with changes from architecture plans. Adds new components, integrations, flows, and ADR references without rewriting existing content. Use after implementation to keep architecture documentation current.