src/skills/x-internal-load-story-context/SKILL.md
Loads a story file, validates predecessors, runs pre-checks, classifies scope.
npx skillsauth add edercnj/ia-dev-environment x-internal-load-story-contextInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
🔒 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 carve-out). Skill da convenção
x-internal-*, na subdirinternal/plan/: o subdir reflete categoria funcional — esta skill orquestra lógica de planejamento (load+classify), não runtime ops (mutate/write).
Carve out Phase 0 of x-implement-story into a single-responsibility,
read-only Bash pipeline. The skill:
.aikittools/features/feature-XXXX/story-XXXX-YYYY.md.DONE (or an accepted synonym: MERGED / COMPLETE)
inside .aikittools/features/feature-XXXX/execution-state.json..aikittools/features/feature-XXXX/plans/ and
classifies each as fresh (artifact mtime ≥ story mtime),
stale (artifact mtime < story mtime), or missing.SIMPLE (≤ 4 tasks), STANDARD (5–7 tasks),
COMPLEX (≥ 8 tasks).PRE_PLANNED (all 7 fresh), HYBRID (some fresh, some stale or
missing), INLINE (zero fresh).The output is a single-line JSON envelope consumed by
x-implement-story to drive the subsequent Phase 0.5 / Phase 1 /
Phase 1.5 branching. The skill never mutates the filesystem.
| Aspect | Value | Rationale |
| :--- | :--- | :--- |
| Path | internal/plan/x-internal-load-story-context/ | internal/ prefix scopes visibility; plan/ aligns with sibling planning-category skills (x-plan-story, x-internal-plan-arch-story) and separates from internal/ops/ siblings that mutate state |
| 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 | Minimal surface; all logic is a read-only shell pipeline built on jq + stat |
| Naming | x-internal-{subject}-{action} | Mirrors Rule 04 skill taxonomy; story-load-context = subject+action |
Audit rule: Rule 22 (Lifecycle Integrity) validates every skill under
internal/** satisfies all 6 anchors above. Violations fail the
LifecycleIntegrityAuditTest.
Bare-slash form is intentionally omitted — this skill is never invoked
by a human typing /x-internal-load-story-context in chat. All
invocations follow Rule 13 INLINE-SKILL pattern from a calling
orchestrator:
Skill(skill: "x-internal-load-story-context",
args: "--story-id story-0049-0011 --feature-id 0049")
| Parameter | Required | Default | Description |
| :--- | :--- | :--- | :--- |
| --story-id <id> | M | — | Story identifier (story-XXXX-YYYY canonical form) |
| --feature-id <id> | C | — | 4-digit feature identifier (XXXX) — used to resolve .aikittools/features/feature-XXXX/. Required when --context-path is not provided. |
| --context-path <path> | C | — | Explicit base directory path (e.g. .aikittools/bugs/bug-000001/). When provided, takes precedence over --feature-id for path resolution. Required when --feature-id is not provided. |
Path resolution rule:
base_dir = context_pathwhen--context-pathis provided; otherwisebase_dir = ".aikittools/features/feature-{feature_id}/". Exactly one of--feature-id/--context-pathmust be supplied.
Template-type aware parsing: When the loaded story file contains
template-type: bug-storyin its frontmatter (or--context-pathpoints to.aikittools/bugs/), parsing adapts to the bug story template layout:
- Dependencies (blocked-by): found in Section 7 (not Section 1 as in feature stories)
- AC/Gherkin scenarios: found in Section 4 (not Section 7)
- Tasks: listed in a markdown table (not
### TASK-XXXX-YYYY-NNN:headers) In bug mode,taskCountcounts table rows under the task table;scenarioCountcountsCenario:/Scenario:markers under Section 4.
When successful, the skill writes a single-line JSON object to stdout:
| Field | Type | Always Present | Description |
| :--- | :--- | :--- | :--- |
| storyFile | String | yes | Absolute path to the resolved story markdown |
| storyMtime | Number | yes | Unix epoch seconds of the story file's last modification |
| dependencies | Array<{id:String, status:String}> | yes | One entry per Section 1 blocker (empty array when Blocked By: —) |
| scope | String | yes | SIMPLE / STANDARD / COMPLEX |
| planningMode | String | yes | PRE_PLANNED / HYBRID / INLINE |
| artifacts | {fresh:Array<String>, stale:Array<String>, missing:Array<String>} | yes | Classification of the 7 planning artifact paths (relative to .aikittools/features/feature-XXXX/plans/) |
| taskCount | Number | yes | Count of ### TASK-XXXX-YYYY-NNN: headers under Section 8 |
| scenarioCount | Number | yes | Count of Cenario: / Scenario: markers under Section 7 |
| planningGate | "PASSED" \| "PLAN_REQUIRED" | yes | PASSED when planningMode=PRE_PLANNED; PLAN_REQUIRED when HYBRID or INLINE. Consumed by x-internal-implement-story to gate task execution. |
| artifacts.missingPlanStory | boolean | yes | true when plan-story-XXXX-YYYY.md is in missing or stale arrays. |
| artifacts.missingPlanTasks | Array<String> | yes | Basenames of plan-task-TASK-XXXX-YYYY-NNN.md files that are missing or stale. Empty array when all task-plan files are fresh. |
The seven planning-artifact basenames scanned under
.aikittools/features/feature-XXXX/plans/ are:
plan-story-XXXX-YYYY.md (implementation plan)arch-story-XXXX-YYYY.md (architecture plan)tests-story-XXXX-YYYY.md (test plan)tasks-story-XXXX-YYYY.md (task breakdown)security-story-XXXX-YYYY.md (security assessment)compliance-story-XXXX-YYYY.md (compliance assessment)task-implementation-map-story-XXXX-YYYY.md (map)| Code | Name | Condition | Message Format |
| :--- | :--- | :--- | :--- |
| 0 | SUCCESS | Context loaded and emitted | — |
| 1 | STORY_NOT_FOUND | .aikittools/features/feature-XXXX/story-XXXX-YYYY.md absent | Story file not found: <path> |
| 2 | DEPENDENCY_NOT_DONE | Any Section 1 blocker reports non-DONE status | Blocker <id> is <status> |
| 3 | FEATURE_NOT_FOUND | .aikittools/features/feature-XXXX/ absent | Feature dir not found: .aikittools/features/feature-<id> |
Parse --story-id, --feature-id, and --context-path; reject unknown flags and missing required flags with exit 64 (sysexits EX_USAGE). Derive:
if [[ -n "${context_path}" ]]; then
base_dir="${context_path%/}" # strip trailing slash if any
else
base_dir=".aikittools/features/feature-${feature_id}"
fi
story_file="${base_dir}/${story_id}.md"
state_file="${base_dir}/execution-state.json"
plans_dir="${base_dir}/plans"
When ${base_dir} is not a directory, exit 3 (FEATURE_NOT_FOUND — reused for any context dir).
When ${story_file} is not a regular file, exit 1 (STORY_NOT_FOUND).
story_mtime=$(stat -f '%m' "${story_file}" 2>/dev/null \
|| stat -c '%Y' "${story_file}")
Extract Section 1 (Dependencies) between the ## 1. Dependências /
## 1. Dependencies header and the next ## header; extract
Section 7 (Acceptance Criteria) and Section 8 (Tasks) likewise. The
parser is tolerant: accepts both Portuguese (Dependências, Cenario)
and English (Dependencies, Scenario) section titles since current
stories under .aikittools/features/feature-XXXX/ are authored in Portuguese.
Section 1 is a markdown table with columns Blocked By | Blocks. The
Blocked By cell is either — (empty) or a comma-separated list of
story IDs. Collect IDs; for each, resolve its status via
execution-state.json:
status=$(jq -r ".stories[\"${dep_id}\"].status // \"UNKNOWN\"" \
"${state_file}")
When state_file is absent, fall back to parsing the dep story's own
**Status:** header inside its markdown file (supports features where
execution-state.json has not yet been initialized). Accepted synonyms
for success: DONE, MERGED, COMPLETE, Concluída, Concluida.
On any blocker with non-DONE status, exit 2 (DEPENDENCY_NOT_DONE)
with the offending ID and status in the error message.
For each of the 7 planning-artifact basenames:
artifact="${plans_dir}/${name}"
if [[ ! -f "${artifact}" ]]; then
missing+=("${name}")
elif [[ $(stat -f '%m' "${artifact}" 2>/dev/null \
|| stat -c '%Y' "${artifact}") -ge ${story_mtime} ]]; then
fresh+=("${name}")
else
stale+=("${name}")
fi
Artifacts not present in the plans directory tree (common in early
stories) populate missing. Artifacts with mtime strictly less than
the story's mtime populate stale. The rest populate fresh.
Count Section 8 tasks via grep -cE '^### TASK-[0-9]{4}-[0-9]{4}-[0-9]{3}:'.
Primary classification by task count:
| Task count | Baseline scope |
| :--- | :--- |
| ≤ 4 | SIMPLE |
| 5–7 | STANDARD |
| ≥ 8 | COMPLEX |
Functional elevation rules (applied after baseline, elevate only — never downgrade):
| Story characteristic | Minimum scope after elevation |
| :--- | :--- |
| compliance: field present and value ≠ none | COMPLEX |
| Contains ALTER TABLE, CREATE TABLE, DROP TABLE, or ADD COLUMN | COMPLEX |
| 3 or more distinct REST endpoints (POST/GET/PUT/DELETE/PATCH /) | STANDARD |
| Any one of the above complex criteria | COMPLEX |
Apply elevation via grep on the story body after computing the baseline. A single COMPLEX criterion is sufficient to elevate to COMPLEX regardless of task count. Elevation is one-way: a COMPLEX baseline is never lowered.
Note: This scope is a structural heuristic derived from story text. Callers (
x-implement-story) MAY apply additional functional elevation based on interface type declarations (rest,grpc,event-consumer,event-producer,websocket) after consuming this envelope. Thescopefield emitted here reflects the best static assessment available without parsing interface-type tables.
Scenario count is computed for telemetry (Section 7 grep -cE '^(Cenario|Scenario):') but does not influence the scope label; reserved for future tuning per story-0049-0011 §3.2.
| Fresh artifact count | Planning mode |
| :--- | :--- |
| 7 (all fresh) | PRE_PLANNED |
| 1–6 (mixed) | HYBRID |
| 0 | INLINE |
After resolving planning_mode, derive the planning gate and the missing plan-file details for downstream use:
# Gate decision: PASSED only when all 7 artifacts are fresh
if [[ "${planning_mode}" == "PRE_PLANNED" ]]; then
planning_gate="PASSED"
else
planning_gate="PLAN_REQUIRED"
fi
# Detect whether the primary story plan is missing or stale
plan_story_basename="plan-story-${story_id_suffix}.md"
if echo "${missing_json}" | grep -qF "${plan_story_basename}" \
|| echo "${stale_json}" | grep -qF "${plan_story_basename}"; then
missing_plan_story=true
else
missing_plan_story=false
fi
# Collect missing/stale task-plan file basenames
# task-plan files match pattern: plan-task-TASK-XXXX-YYYY-NNN.md
missing_plan_tasks_json=$(jq -n \
--argjson missing "${missing_json}" \
--argjson stale "${stale_json}" \
'($missing + $stale) | map(select(test("^plan-task-TASK-")))' \
2>/dev/null || echo "[]")
Compose the response using jq -n so the shape is authoritative and
the stdout stream is a single line terminated by \n:
jq -nc \
--arg storyFile "${story_file}" \
--argjson storyMtime "${story_mtime}" \
--argjson dependencies "${deps_json}" \
--arg scope "${scope}" \
--arg planningMode "${mode}" \
--argjson artifacts "${artifacts_json}" \
--argjson taskCount "${task_count}" \
--argjson scenarioCount "${scenario_count}" \
--arg planningGate "${planning_gate}" \
--argjson missingPlanStory "${missing_plan_story}" \
--argjson missingPlanTasks "${missing_plan_tasks_json}" \
'{storyFile:$storyFile, storyMtime:$storyMtime,
dependencies:$dependencies, scope:$scope,
planningMode:$planningMode,
artifacts:($artifacts | . + {missingPlanStory:$missingPlanStory, missingPlanTasks:$missingPlanTasks}),
taskCount:$taskCount, scenarioCount:$scenarioCount,
planningGate:$planningGate}'
Exit 0.
Skill(skill: "x-internal-load-story-context",
args: "--story-id story-0049-0001 --feature-id 0049")
Output:
{"storyFile":".aikittools/features/feature-XXXX/story-XXXX-0001.md","storyMtime":1745343600,"dependencies":[],"scope":"SIMPLE","planningMode":"INLINE","artifacts":{"fresh":[],"stale":[],"missing":["plan-story-XXXX-0001.md","arch-story-XXXX-0001.md","tests-story-XXXX-0001.md","tasks-story-XXXX-0001.md","security-story-XXXX-0001.md","compliance-story-XXXX-0001.md","task-implementation-map-story-XXXX-0001.md"]},"taskCount":3,"scenarioCount":4}
Exit: 0.
Skill(skill: "x-internal-load-story-context",
args: "--story-id story-0049-0008 --feature-id 0049")
Stderr:
Blocker story-0049-0001 is PENDING
Exit: 2.
Given all 7 artifacts under .aikittools/features/feature-XXXX/plans/ with mtime ≥ story
mtime, output includes "planningMode":"PRE_PLANNED" and every
artifact in artifacts.fresh.
Exit: 0.
Output contains "scope":"COMPLEX" and "taskCount":10. Exit: 0.
Skill(skill: "x-internal-load-story-context",
args: "--story-id story-9999-0001 --feature-id 9999")
Stderr:
Feature dir not found: .aikittools/features/feature-XXXX
Exit: 3.
When the feature directory exists but the story file does not:
Stderr:
Story file not found: .aikittools/features/feature-XXXX/story-XXXX-9999.md
Exit: 1.
| Artifact | Path | Description | | :--- | :--- | :--- | | Response envelope | stdout | Single-line JSON matching the Response Contract | | Error diagnostic | stderr | Single line, non-empty only on exit ≠ 0 |
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).
| Scenario | Action |
| :--- | :--- |
| Missing required flag | Print usage: banner to stderr; exit 64 (sysexits EX_USAGE) |
| jq absent on PATH | Exit 127 with jq is required; abort before any stat |
| stat flavour mismatch (GNU vs BSD) | Try stat -f %m (BSD) first, fall back to stat -c %Y (GNU) |
| execution-state.json absent | Fall back to parsing each dep story's own **Status:** header |
| Dep story file absent AND state file absent | Treat dep status as UNKNOWN; exit 2 |
| Section 1 table unparseable (malformed markdown) | Treat dependencies as empty; log warning to stderr; continue |
| Unknown section-title language | Try both Dependências / Dependencies, Cenario / Scenario; stop at first hit |
Target: < 500 ms for a typical feature with 20 stories in
execution-state.json and all 7 planning artifacts present. The
implementation reads at most 9 files (1 story, 1 state, 7 artifacts
via stat only — no body read) and spawns a single jq invocation
for JSON assembly. No network I/O; no recursive traversal beyond
.aikittools/features/feature-XXXX/plans/.
The story ships the following acceptance test scenarios, mirroring Section 7 of story-0049-0011:
story-0049-0001 reports
dependencies=[] and valid scope / planningMode.PENDING status → exit 2, message "Blocker <id> is PENDING".planningMode=PRE_PLANNED.--story-id story-9999-0001 with feature
dir absent → exit 3; feature dir present but story missing → exit 1.scope=COMPLEX.Goldens under
src/test/resources/golden/internal/plan/x-internal-load-story-context/
lock the SKILL.md rendering. Coverage requirement: ≥ 95% line /
≥ 90% branch across the invoking Bash codepaths.
The ia-dev-env generator MUST exclude skills with
visibility: internal from:
src/README.md skill-inventory table./help menu listing surfaced by Claude Code.Internal skills are still copied into src/skills/ (flat layout)
so Skill(skill: "x-internal-...") invocations from other skills
resolve correctly. The invariant: user cannot see them; orchestrators
can invoke them.
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 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).
| Skill | Relationship | Context |
| :--- | :--- | :--- |
| x-implement-story | caller (primary) | Phase 0 carve-out: the skill's stdout envelope replaces ~140 inline lines previously executed by the orchestrator |
| x-internal-update-status | peer | Sibling x-internal-* skill; this skill reads execution-state.json, the other mutates it; both acquire the same <file>.lock when their lifetimes overlap (this skill uses flock -s shared lock) |
| x-reconcile-status | consumer (peer) | Reads the same story **Status:** header this skill inspects; no shared mutation |
Downstream stories that depend on this carve-out: story-0049-0019 (orchestrator consumes the envelope and deletes the inline Phase 0 block).
Full workflow detail (argument-parser rejection matrix, section-extraction
regex catalogue, artifact-classification edge cases, and the
execution-state.json schema contract) lives in
references/full-protocol.md per
ADR-0011.
development
Documentation freshness gate: validates 6 dimensions (readme, api, adr, etc.) per PR.
testing
Conditional dep-policy gate: CVEs, licenses, versions, freshness; SARIF + report.
documentation
Incrementally updates the service or system architecture document; never regenerative.
development
Scans code and git history for leaked credentials, API keys, and tokens; SARIF output.