@jenga-ai/agent 1.2.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +97 -91
  2. package/agents/developer.md +26 -7
  3. package/agents/scrum-master.md +57 -22
  4. package/agents/tester.md +68 -4
  5. package/hooks/on_session_end.sh +40 -1
  6. package/lib/generate-agent-context.js +18 -1
  7. package/lib/generate-copilot-instructions.js +18 -1
  8. package/lib/generate-skill-allow-list.js +191 -0
  9. package/lib/skill-allow-list.json +43 -0
  10. package/package.json +35 -20
  11. package/scripts/apply-j-prefix.sh +230 -0
  12. package/scripts/consume-context-digest.sh +103 -0
  13. package/scripts/postinstall.js +25 -0
  14. package/scripts/sweep-stale-context-digests.sh +132 -0
  15. package/scripts/validate-board.sh +5 -0
  16. package/scripts/write-context-digest.sh +230 -0
  17. package/skills/brainstorm/SKILL.md +1 -1
  18. package/skills/btw/SKILL.md +1 -1
  19. package/skills/clearify/SKILL.md +1 -1
  20. package/skills/close-story/SKILL.md +78 -6
  21. package/skills/close-story/scripts/check-privatized.sh +345 -0
  22. package/skills/commit/SKILL.md +12 -2
  23. package/skills/continue/SKILL.md +1 -1
  24. package/skills/deep-dive/SKILL.md +1 -1
  25. package/skills/dev-done/SKILL.md +46 -0
  26. package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
  27. package/skills/distribute/SKILL.md +1 -1
  28. package/skills/do/SKILL.md +100 -10
  29. package/skills/doc/README.md +155 -0
  30. package/skills/doc/SKILL.md +43 -13
  31. package/skills/doc/authoring-notes.md +72 -0
  32. package/skills/doc/scripts/resolve_last_update.py +149 -0
  33. package/skills/doc-sync/SKILL.md +1 -1
  34. package/skills/dooo/SKILL.md +1 -1
  35. package/skills/error/SKILL.md +1 -1
  36. package/skills/evaluate/SKILL.md +1 -1
  37. package/skills/examplify/SKILL.md +1 -1
  38. package/skills/help/SKILL.md +1 -1
  39. package/skills/idea/SKILL.md +1 -1
  40. package/skills/improve/SKILL.md +1 -1
  41. package/skills/init/SKILL.md +8 -7
  42. package/skills/init/assets/scope-thresholds_template.json +7 -0
  43. package/skills/init/scripts/init.sh +6 -0
  44. package/skills/j-init/SKILL.md +168 -0
  45. package/skills/j-init/assets/.gitignore_template +15 -0
  46. package/skills/j-init/assets/PROJECT_SUMMARY_template.md +13 -0
  47. package/skills/j-init/assets/directory_structure.txt +14 -0
  48. package/skills/j-init/assets/scope-thresholds_template.json +7 -0
  49. package/skills/j-init/assets/strategy_stub_template.md +38 -0
  50. package/skills/j-init/assets/test-config_template.json +4 -0
  51. package/skills/j-init/assets/workflow_template.json +30 -0
  52. package/skills/j-init/scripts/apply-project-visibility.sh +176 -0
  53. package/skills/j-init/scripts/detect-existing-codebase.sh +166 -0
  54. package/skills/j-init/scripts/init.sh +116 -0
  55. package/skills/jbp/SKILL.md +1 -1
  56. package/skills/jenga/SKILL.md +1 -1
  57. package/skills/jenga/scripts/render-confirmation.sh +55 -18
  58. package/skills/jenga-permission-level/SKILL.md +1 -1
  59. package/skills/lgtm/SKILL.md +1 -1
  60. package/skills/pi-plan/SKILL.md +1 -1
  61. package/skills/proceed/SKILL.md +1 -1
  62. package/skills/publish/SKILL.md +67 -1
  63. package/skills/publish/adapters/npm-ci.md +60 -4
  64. package/skills/publish/adapters/npm.md +18 -0
  65. package/skills/publish/assets/ci-contract.md +27 -0
  66. package/skills/publish/assets/publish.example.json +27 -0
  67. package/skills/publish/schemas/publish.schema.json +20 -0
  68. package/skills/publish/scripts/npm_ci_pipeline.sh +50 -1
  69. package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
  70. package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
  71. package/skills/publish/scripts/publish_common.sh +16 -0
  72. package/skills/publish/scripts/show_history.sh +12 -5
  73. package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
  74. package/skills/publish/scripts/write_ledger_entry.sh +92 -2
  75. package/skills/reconcile/SKILL.md +122 -12
  76. package/skills/reconcile/assets/report_format.md +17 -0
  77. package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
  78. package/skills/reconcile-origin/SKILL.md +1 -1
  79. package/skills/redo/SKILL.md +1 -1
  80. package/skills/skillify/SKILL.md +1 -1
  81. package/skills/spinoff/SKILL.md +1 -1
  82. package/skills/status/SKILL.md +1 -1
  83. package/skills/todo/SKILL.md +40 -3
  84. package/skills/todo/scripts/add_trivial_task.sh +216 -0
  85. package/skills/todo/scripts/update_story_tasks.py +87 -0
  86. package/skills/uncharted/SKILL.md +201 -22
  87. package/skills/uncharted/scripts/directory-triage.sh +342 -0
  88. package/skills/uncharted/scripts/elicitation-state.sh +457 -0
  89. package/skills/wtf/SKILL.md +1 -1
  90. package/templates/SCRUM_BOARD_SCHEMA.md +90 -2
  91. package/templates/agent-context.md.tpl +47 -12
  92. package/templates/copilot-instructions.md.tpl +36 -9
  93. package/mcp/router/README.md +0 -19
  94. package/mcp/router/embedder.js +0 -23
  95. package/mcp/router/index.js +0 -204
  96. package/mcp/router/matcher.js +0 -87
  97. package/mcp/router/package-lock.json +0 -1048
  98. package/mcp/router/package.json +0 -11
  99. package/mcp/router/skill-index.js +0 -104
  100. package/skills/route/SKILL.md +0 -180
@@ -6,10 +6,36 @@ source "$SCRIPT_DIR/publish_common.sh"
6
6
 
7
7
  usage() {
8
8
  cat <<'USAGE'
9
- Usage: write_ledger_entry.sh <target> <adapter> <platform_state> <notes_path> [--yes] [--dry-run] [--version <vX.Y.Z>] [--config <path>]
9
+ Usage: write_ledger_entry.sh <target> <adapter> <platform_state> <notes_path> [--yes] [--dry-run] [--version <vX.Y.Z>] [--config <path>] [--stage-id <id>] [--dist-tag <tag>] [--result <pass|fail>] [--reason <text>]
10
+
11
+ platform_state must be one of: uploaded, partial, dry-run, failed, staged,
12
+ stage_tested, approved, rejected.
13
+
14
+ --result <pass|fail> Optional. Records a stage_tested smoke-test outcome on
15
+ the entry (null when omitted). The npm_stage_inspect.sh
16
+ approve interlock reads this field.
17
+ --reason <text> Optional. Records a free-text reason on the entry —
18
+ used by npm_stage_inspect.sh's `approve --force
19
+ <reason>` to record why the test interlock was
20
+ overridden (null when omitted).
10
21
  USAGE
11
22
  }
12
23
 
24
+ # Full accepted platform_state vocabulary: the three pre-existing states
25
+ # actually written by publish_deploy.sh/reconcile_tags.sh today (uploaded,
26
+ # partial, dry-run) plus `failed`, documented as part of the original
27
+ # ledger-entry format (E22_S05_T02) though not currently emitted by any
28
+ # caller, plus the four staged-publishing states added by E22_S09_T02.
29
+ ALLOWED_PLATFORM_STATES=(uploaded partial dry-run failed staged stage_tested approved rejected)
30
+
31
+ platform_state_is_valid() {
32
+ local state="$1" allowed
33
+ for allowed in "${ALLOWED_PLATFORM_STATES[@]}"; do
34
+ [[ "$state" == "$allowed" ]] && return 0
35
+ done
36
+ return 1
37
+ }
38
+
13
39
  [[ $# -ge 4 ]] || { usage >&2; exit 1; }
14
40
  TARGET_NAME="$1"
15
41
  ADAPTER_NAME="$2"
@@ -21,6 +47,10 @@ AUTO_ACCEPT=0
21
47
  DRY_RUN=0
22
48
  SELECTED_VERSION=''
23
49
  CONFIG_PATH="${PUBLISH_CONFIG:-}"
50
+ STAGE_ID=''
51
+ DIST_TAG=''
52
+ RESULT=''
53
+ REASON=''
24
54
 
25
55
  while [[ $# -gt 0 ]]; do
26
56
  case "$1" in
@@ -42,6 +72,26 @@ while [[ $# -gt 0 ]]; do
42
72
  CONFIG_PATH="$2"
43
73
  shift 2
44
74
  ;;
75
+ --stage-id)
76
+ [[ $# -ge 2 ]] || { usage >&2; exit 1; }
77
+ STAGE_ID="$2"
78
+ shift 2
79
+ ;;
80
+ --dist-tag)
81
+ [[ $# -ge 2 ]] || { usage >&2; exit 1; }
82
+ DIST_TAG="$2"
83
+ shift 2
84
+ ;;
85
+ --result)
86
+ [[ $# -ge 2 ]] || { usage >&2; exit 1; }
87
+ RESULT="$2"
88
+ shift 2
89
+ ;;
90
+ --reason)
91
+ [[ $# -ge 2 ]] || { usage >&2; exit 1; }
92
+ REASON="$2"
93
+ shift 2
94
+ ;;
45
95
  --help|-h)
46
96
  usage
47
97
  exit 0
@@ -54,6 +104,14 @@ while [[ $# -gt 0 ]]; do
54
104
  done
55
105
 
56
106
  [[ -n "$TARGET_NAME" && -n "$ADAPTER_NAME" && -n "$PLATFORM_STATE" ]] || { usage >&2; exit 1; }
107
+ if ! platform_state_is_valid "$PLATFORM_STATE"; then
108
+ printf "Unknown platform_state '%s'. Expected one of: %s.\n" "$PLATFORM_STATE" "${ALLOWED_PLATFORM_STATES[*]}" >&2
109
+ exit 1
110
+ fi
111
+ if [[ -n "$RESULT" && "$RESULT" != "pass" && "$RESULT" != "fail" ]]; then
112
+ printf "Unknown --result '%s'. Expected 'pass' or 'fail'.\n" "$RESULT" >&2
113
+ exit 1
114
+ fi
57
115
  command -v jq >/dev/null 2>&1 || { echo 'jq is required.' >&2; exit 1; }
58
116
 
59
117
  HISTORY_FILE="$(publish_resolve_history_file "$CONFIG_PATH")"
@@ -98,6 +156,30 @@ else
98
156
  NOTES_JSON="$(jq -Rn --arg path "$NOTES_PATH_RAW" '$path')"
99
157
  fi
100
158
 
159
+ if [[ -z "$STAGE_ID" ]]; then
160
+ STAGE_ID_JSON='null'
161
+ else
162
+ STAGE_ID_JSON="$(jq -Rn --arg id "$STAGE_ID" '$id')"
163
+ fi
164
+
165
+ if [[ -z "$DIST_TAG" ]]; then
166
+ DIST_TAG_JSON='null'
167
+ else
168
+ DIST_TAG_JSON="$(jq -Rn --arg tag "$DIST_TAG" '$tag')"
169
+ fi
170
+
171
+ if [[ -z "$RESULT" ]]; then
172
+ RESULT_JSON='null'
173
+ else
174
+ RESULT_JSON="$(jq -Rn --arg result "$RESULT" '$result')"
175
+ fi
176
+
177
+ if [[ -z "$REASON" ]]; then
178
+ REASON_JSON='null'
179
+ else
180
+ REASON_JSON="$(jq -Rn --arg reason "$REASON" '$reason')"
181
+ fi
182
+
101
183
  ENTRY_JSON="$(jq -cn \
102
184
  --arg id "$ENTRY_ID" \
103
185
  --arg version "$VERSION" \
@@ -109,6 +191,10 @@ ENTRY_JSON="$(jq -cn \
109
191
  --arg git_tag "$GIT_TAG" \
110
192
  --arg commit_sha "$COMMIT_SHA" \
111
193
  --argjson release_notes_path "$NOTES_JSON" \
194
+ --argjson stage_id "$STAGE_ID_JSON" \
195
+ --argjson dist_tag "$DIST_TAG_JSON" \
196
+ --argjson result "$RESULT_JSON" \
197
+ --argjson reason "$REASON_JSON" \
112
198
  '{
113
199
  id: $id,
114
200
  version: $version,
@@ -119,7 +205,11 @@ ENTRY_JSON="$(jq -cn \
119
205
  release_notes_path: $release_notes_path,
120
206
  platform_state: $platform_state,
121
207
  git_tag: $git_tag,
122
- commit_sha: $commit_sha
208
+ commit_sha: $commit_sha,
209
+ stage_id: $stage_id,
210
+ dist_tag: $dist_tag,
211
+ result: $result,
212
+ reason: $reason
123
213
  }')"
124
214
 
125
215
  publish_append_history_entry "$HISTORY_FILE" "$ENTRY_JSON"
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: reconcile
2
+ name: j:reconcile
3
3
  description: Reconcile the scrum board with actual implementation state. Cross-checks every task's board status against git history and worktrees, merges orphaned worktree branches, demotes unimplemented "Done" items, promotes secretly-implemented items, flags code with no board provenance and offers /uncharted segment for it, and cleans stale entries from todo.md. Use when the board feels out of sync, after a big merge session, when tasks were completed outside the normal workflow, or when todo.md has grown stale. Trigger on phrases like "sync the board", "clean up the board", "reconcile", "board is out of date", "todo is stale", or "check what's really done".
4
4
  metadata:
5
5
  prefered_agent: scrum-master
@@ -9,21 +9,98 @@ metadata:
9
9
 
10
10
  Walks the full board (epics → stories → tasks), verifies each item's status against what actually exists in git, and fixes any drift. Also cleans `project/todo.md` of entries that are already done. Then runs the same check in reverse — code that exists with no board item and no EST-tagged commit behind it — and offers `/uncharted segment` for what it finds.
11
11
 
12
+ ## Scope argument
13
+
14
+ `/reconcile` optionally accepts a single scope argument that bounds every phase below to a subset
15
+ of the board instead of always walking the whole thing:
16
+
17
+ | Argument | Resolves to |
18
+ |---|---|
19
+ | _(none)_ | Full board — unchanged from today's behavior. |
20
+ | `E12` (bare epic id) | That epic, in full. |
21
+ | `E12_S03` or `S03` (bare story id) | The story's **containing epic, in full** — not just the named story. See "Default-scope-to-epic rule" below. |
22
+ | `E12_S03_T01` (bare task id) | The task's **containing epic, in full** — same default-scope-to-epic rule. |
23
+ | `S03-05` or `E12_S03-05` (a story range) | **Exactly** the named stories (and their tasks), plus rollup limited to only the epic(s) those stories belong to. Does not expand to unrelated stories in the same epic(s). Only story-level ranges are supported — an epic-level range (`E01-03`) or a task-level range (`T01-03`) is rejected with a clear reason. |
24
+ | Anything unresolvable (unknown id, malformed range, an ambiguous partial) | A clear error before any board file is touched — no partial result, no silent full-board fallback. |
25
+
26
+ **Default-scope-to-epic rule (settled — do not relitigate):** a scope argument naming a story or
27
+ task (not an epic) always resolves to that item's *containing epic, in full* — never just the
28
+ named story/task in isolation. Story and epic rollup (Phase 4) cannot be evaluated correctly
29
+ without seeing all sibling stories/tasks, so scoping to a lone story or task would risk an
30
+ incorrect rollup decision. If you want to reconcile *exactly* a set of stories without pulling in
31
+ the rest of their epic(s), use the range syntax (`S03-05`) instead — a range does not apply the
32
+ default-scope-to-epic expansion.
33
+
34
+ Omitting the argument entirely keeps today's full-board behavior completely unchanged, in every
35
+ phase below.
36
+
37
+ Scope resolution itself is deterministic and handled entirely by
38
+ `skills/reconcile/scripts/resolve-reconcile-scope.sh` (see Phase 0 immediately below) — this skill
39
+ only interprets that script's output; it does not re-parse scope arguments or re-derive range
40
+ expansion.
41
+
12
42
  ## Instructions
13
43
 
14
44
  ### 0. Read configuration
15
45
  Read `project/configs/workflow.json` for board paths. Fall back to `project/board/` if missing.
16
46
  The statuses that count as "completed" are: **Done**, **Passed**, **Passed with remarks**.
17
47
 
48
+ ### Phase 0 — Resolve scope
49
+ Before touching anything else, resolve the scope argument (if any — see "Scope argument" above)
50
+ into a concrete set of epic/story/task ids:
51
+
52
+ ```bash
53
+ skills/reconcile/scripts/resolve-reconcile-scope.sh <scope-argument>
54
+ ```
55
+
56
+ Omit `<scope-argument>` entirely for an unscoped, full-board run (the script's own no-argument
57
+ path, matching today's default).
58
+
59
+ - **On `{"status":"error","reason":"..."}` (non-zero exit)** — stop immediately. Report the
60
+ `reason` to the user as the reconcile failure. Do **not** proceed to Phase 1, and do not touch
61
+ any board file, `project/todo.md`, or write a reconcile report. This is the enforcement point
62
+ for the story's AC: an invalid or unresolvable scope argument fails with a clear error before
63
+ any board file is touched — no partial result and no silent full-board fallback.
64
+ - **On success** — stdout is a single JSON object:
65
+ ```json
66
+ {
67
+ "scope_type": "full" | "epic" | "range",
68
+ "epic_ids": [...],
69
+ "story_ids": [...],
70
+ "task_ids": [...],
71
+ "owned_path_hints": [...]
72
+ }
73
+ ```
74
+ Carry `scope_type`, `epic_ids`, `story_ids`, `task_ids`, and `owned_path_hints` forward as **the
75
+ resolved scope** through every phase below. When `scope_type` is `"full"`, every phase runs
76
+ exactly as documented for the unscoped case — nothing below changes behavior or output. When
77
+ `scope_type` is `"epic"` or `"range"`, each phase's "when scoped" instructions apply instead.
78
+
79
+ `owned_path_hints` is best-effort and non-authoritative (see Phase 5) — treat it only as a
80
+ positive filter, never as proof that an unlisted path is unrelated to the scope.
81
+
18
82
  ### 1. Snapshot the board
19
- Scan every file in `epics/`, `stories/`, and `tasks/`. For each item record:
83
+ **When unscoped (`scope_type: "full"`):** scan every file in `epics/`, `stories/`, and `tasks/`,
84
+ exactly as before.
85
+
86
+ **When scoped (`scope_type: "epic"` or `"range"`):** only scan the board files whose id is in the
87
+ resolved `epic_ids` / `story_ids` / `task_ids` — for `"epic"` scope this is the epic file itself
88
+ plus every story/task file under it; for `"range"` scope this is exactly the named story/task
89
+ files plus the epic file(s) in `epic_ids` (the epic(s) those stories belong to). Files for
90
+ out-of-scope items are not scanned and play no further part in this run.
91
+
92
+ For each scanned item, record:
20
93
  - `id`, `title`, `status` (the **pre-reconcile** status — needed in phase 4)
21
94
  - `date_completed` (if set)
22
95
 
23
- Also read `project/todo.md` and parse every non-comment, non-blank line into a list of todo entries.
96
+ Also read `project/todo.md`. When unscoped, parse every non-comment, non-blank line into the list
97
+ of todo entries, as before. When scoped, only parse lines that reference an id in the resolved
98
+ scope (`epic_ids` / `story_ids` / `task_ids`) — out-of-scope lines are left out of the list
99
+ entirely, which is what keeps Phase 6 from touching them later.
24
100
 
25
101
  ### 2. Verify "completed" tasks — are they really implemented?
26
- For every task whose status is a completed status:
102
+ For every task recorded in Phase 1 (i.e. every task on the board when unscoped, or only the
103
+ in-scope tasks when scoped) whose status is a completed status:
27
104
 
28
105
  1. **Search git history** — run `git log --all --oneline --grep="<task_id>"` (e.g. `E01_S01_T01`). A matching commit is strong evidence of implementation.
29
106
  2. **Check documentation artefacts** — look for a plan or summary file under `project/documentation/plans/` or `project/documentation/summaries/` whose name contains the task ID.
@@ -44,7 +121,8 @@ If implementation **cannot be confirmed**:
44
121
  - Report the demotion.
45
122
 
46
123
  ### 3. Verify "incomplete" tasks — are they secretly implemented?
47
- For every task whose status is **not** a completed status (Pending, In Progress, Running, Blocked, etc.):
124
+ For every task recorded in Phase 1 (i.e. every task on the board when unscoped, or only the
125
+ in-scope tasks when scoped) whose status is **not** a completed status (Pending, In Progress, Running, Blocked, etc.):
48
126
 
49
127
  1. **Search git history** for commits referencing the task ID.
50
128
  2. **Check documentation artefacts** as in phase 2.
@@ -62,14 +140,25 @@ If implementation **is confirmed**:
62
140
  If implementation **is not confirmed** — no action needed; the status is already correct.
63
141
 
64
142
  ### 4. Roll up story and epic statuses
65
- After all tasks have been reconciled:
143
+ After all tasks recorded in Phase 1 have been reconciled (every task on the board when unscoped,
144
+ or only the in-scope tasks when scoped):
66
145
 
67
- - For each **story**: if all of its tasks are now in a completed status, set the story to **Done** (if not already). If any task was demoted, and the story was previously completed, set the story back to **In Progress**.
68
- - For each **epic**: apply the same roll-up logic over its stories.
146
+ - **When unscoped:** roll up every story and every epic on the board, exactly as before.
147
+ - **When scoped (`scope_type: "epic"`):** roll up only the in-scope stories and the one in-scope
148
+ epic.
149
+ - **When scoped (`scope_type: "range"`):** roll up only the in-scope stories, and only the
150
+ epic(s) in `epic_ids` (the epic(s) those named stories belong to) — never an unrelated story in
151
+ the same epic(s) that wasn't itself named in the range, per the story's AC.
152
+
153
+ For each story being rolled up: if all of its tasks are now in a completed status, set the story
154
+ to **Done** (if not already). If any task was demoted, and the story was previously completed, set
155
+ the story back to **In Progress**. For each epic being rolled up: apply the same roll-up logic
156
+ over its stories.
69
157
 
70
158
  #### DoD Gap Detection
71
159
 
72
- After rolling up statuses, scan every story whose status is a completed status (`Passed`, `Passed with remarks`, `Done`) for unchecked Definition of Done items:
160
+ After rolling up statuses, scan every story being rolled up (every completed-status story on the
161
+ board when unscoped, or only the in-scope stories when scoped) whose status is a completed status (`Passed`, `Passed with remarks`, `Done`) for unchecked Definition of Done items:
73
162
 
74
163
  1. Read the story file and locate the `## Definition of Done` section. If the section is absent, skip this story gracefully (no error).
75
164
  2. Scan the DoD section for any lines matching `^- \[ \]` (unchecked checkboxes).
@@ -108,6 +197,22 @@ re-derive linkage yourself, and do not substitute a `grep` over `project/board/`
108
197
  fails — a second answer to "is this path on the board" is what that reuse exists to prevent.
109
198
  If the script exits non-zero, report the failure in the reconcile report and continue to phase 6.
110
199
 
200
+ #### Scoping (best-effort, not authoritative)
201
+ `detect-unlinked-code.sh` itself takes no scope argument and is never modified by this phase — it
202
+ always scans the whole repository, exactly as it does today. When a scope was resolved in Phase 0
203
+ and `scope_type` is **not** `"full"`, filter the *report* (not the script's own output) down to
204
+ paths that fall under one of the resolved scope's `owned_path_hints` prefixes before presenting
205
+ `groups[]` / `covered_groups[]` / `not_checked[]` to the user; a path outside every
206
+ `owned_path_hints` prefix is left out of the scoped report entirely.
207
+
208
+ This filtering happens only in this judgement/presentation layer, and it is explicitly
209
+ **best-effort, not authoritative**: `owned_path_hints` is derived only from each in-scope item's
210
+ own `docs:` frontmatter list (see `resolve-reconcile-scope.sh`'s own contract), so it can be empty
211
+ or simply not mention every path the scope actually owns. State this caveat directly in the report
212
+ (see `assets/report_format.md`) — never claim a path is "outside the epic" solely because it
213
+ didn't happen to appear in `owned_path_hints`; say the filtering is best-effort instead. When
214
+ `scope_type: "full"`, this phase is completely unchanged — report every finding, unfiltered.
215
+
111
216
  #### Reading the result
112
217
 
113
218
  - **`groups[]`** — the actionable findings, sorted by `unlinked_count` descending. Each entry is
@@ -176,15 +281,20 @@ phase. If the user picks option 1 or 2, finish the reconcile pass first, then ha
176
281
  `/uncharted segment` at the end so the board is consistent before new items are written.
177
282
 
178
283
  ### 6. Clean `project/todo.md`
179
- Walk the todo entries parsed in phase 1:
284
+ Walk the todo entries parsed in phase 1 — when scoped, that list already excludes any entry that
285
+ doesn't reference an in-scope id (Phase 1 only parsed in-scope lines), so this phase never removes
286
+ or comments out an out-of-scope entry even if it would otherwise qualify under the rules below.
287
+ When unscoped, every entry from phase 1 is eligible, exactly as before.
180
288
 
181
289
  - **Already-done entries** — if an entry references a task/story/epic whose pre-reconcile status (from the snapshot in phase 1) was already a completed status **and** whose implementation has been confirmed (phase 2), **remove the line entirely** from `project/todo.md`.
182
290
  - **Newly-reconciled entries** — entries that were commented out in phase 3 stay as `<!-- RECONCILED: ... -->`.
183
291
  - If `project/todo.md` is left with only the header, the format comment, and blank lines, delete the file.
184
292
 
185
293
  ### 7. Print a summary
186
- Output a reconciliation report using the format in `assets/report_format.md`, with one
187
- additional section from phase 5 placed just before `TODO CLEANUP`:
294
+ Output a reconciliation report using the format in `assets/report_format.md` — the report's very
295
+ first substantive line is always `Scope: ...`, reflecting the scope resolved in Phase 0 (see that
296
+ file's Section rules for the exact forms), so a scoped run is never mistaken for a full pass —
297
+ with one additional section from phase 5 placed just before `TODO CLEANUP`:
188
298
 
189
299
  ```
190
300
  🗺️ UNLINKED CODE (no board item for the files or their directory)
@@ -5,6 +5,8 @@
5
5
  RECONCILIATION REPORT
6
6
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7
7
 
8
+ Scope: <resolved scope — see forms below>
9
+
8
10
  📊 Scanned: <N> epics, <N> stories, <N> tasks
9
11
 
10
12
  ⬇️ DEMOTED (were Done/Passed → now Pending)
@@ -37,8 +39,23 @@
37
39
 
38
40
  ### Section rules
39
41
 
42
+ - `Scope:` is always the very first substantive line of the report — above `📊 Scanned:` — so a
43
+ scoped run is never mistaken for a full pass. Its exact form depends on the scope resolved in
44
+ `/reconcile`'s Phase 0:
45
+ - Unscoped run (`scope_type: "full"`): `Scope: full board`
46
+ - Epic scope (`scope_type: "epic"`): `Scope: E12 (full epic)`
47
+ - Range scope (`scope_type: "range"`): `Scope: S03-05 (2 stories, epic E12)` — name the epic(s)
48
+ the range's resolved stories belong to (`epic_ids`); if the range spans more than one epic,
49
+ list all of them, e.g. `Scope: S03-05 (3 stories, epics E12, E14)`.
50
+ When `Scope:` is not `full board`, the `📊 Scanned:` line's counts reflect the resolved scope's
51
+ own epics/stories/tasks only — not the whole board's.
40
52
  - Omit any section that has zero items (e.g. if nothing was demoted, skip the DEMOTED block entirely).
41
53
  - The MERGED section should include the branch name that was merged.
42
54
  - The TODO CLEANUP section is always shown if `project/todo.md` existed at the start, even if zero changes were made (in that case show all counts as 0).
43
55
  - If `project/todo.md` did not exist, omit the TODO CLEANUP section.
44
56
  - The DOD GAPS section is omitted if no completed stories have unchecked DoD checkboxes.
57
+ - When `Scope:` is not `full board`, the UNLINKED CODE section's `groups[]` / `covered_groups[]` /
58
+ `not_checked[]` have been filtered to the resolved scope's `owned_path_hints` on a best-effort,
59
+ non-authoritative basis (see `/reconcile`'s Phase 5) — append "(scope-filtered, best-effort)" to
60
+ the `🗺️ UNLINKED CODE` heading in that case, since `owned_path_hints` can be incomplete and a
61
+ path missing from it is not proof the path lies outside the scope.