@jenga-ai/agent 1.0.1 → 1.1.1

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 (117) hide show
  1. package/README.md +10 -7
  2. package/agents/developer.md +82 -2
  3. package/agents/scrum-master.md +215 -21
  4. package/agents/tester.md +90 -8
  5. package/hooks/on_session_end.sh +171 -20
  6. package/mcp/router/embedder.js +1 -1
  7. package/mcp/training_runner/index.js +239 -0
  8. package/mcp/training_runner/package-lock.json +1065 -0
  9. package/mcp/training_runner/package.json +15 -0
  10. package/package.json +14 -16
  11. package/scripts/check-permission-level.sh +107 -0
  12. package/scripts/check-publicignore-match.sh +122 -0
  13. package/scripts/check-worktree-liveness.sh +193 -0
  14. package/scripts/generate-rapport-manifest.sh +43 -0
  15. package/scripts/idea_manager.sh +47 -0
  16. package/scripts/install-worktree-commit-guard.sh +134 -0
  17. package/scripts/jenga-permission-level-switch.sh +109 -0
  18. package/scripts/smoke-harness.sh +139 -0
  19. package/scripts/validate-board.sh +62 -0
  20. package/scripts/with-lock.sh +158 -0
  21. package/scripts/worktree-remove-guard.sh +204 -0
  22. package/skills/clearify/SKILL.md +52 -0
  23. package/skills/close-story/SKILL.md +203 -0
  24. package/skills/close-story/scripts/check-story-closeable.sh +195 -0
  25. package/skills/close-story/scripts/compute-scope-divergence.sh +128 -0
  26. package/skills/close-story/scripts/extract-diff-stats.sh +48 -0
  27. package/skills/close-story/scripts/extract-task-diff-stats.sh +97 -0
  28. package/skills/close-story/scripts/update-task-frontmatter.sh +103 -0
  29. package/skills/commit/SKILL.md +30 -3
  30. package/skills/distribute/CONFIG_SCHEMA.md +148 -0
  31. package/skills/distribute/SKILL.md +173 -0
  32. package/skills/distribute/scripts/check-version.sh +74 -0
  33. package/skills/distribute/scripts/commit-version-bump.sh +108 -0
  34. package/skills/distribute/scripts/distribute-changes.sh +381 -0
  35. package/skills/do/SKILL.md +352 -1
  36. package/skills/do/assets/intent-vs-diff-prompt.md +69 -0
  37. package/skills/doc/assets/path-objectives.yaml +13 -0
  38. package/skills/doc-sync/SKILL.md +16 -0
  39. package/skills/doc-sync/assets/doc_targets.md +11 -0
  40. package/skills/idea/SKILL.md +56 -0
  41. package/skills/idea/assets/idea_handoff_template.md +26 -0
  42. package/skills/idea/assets/idea_template.md +3 -0
  43. package/skills/init/SKILL.md +101 -7
  44. package/skills/init/assets/directory_structure.txt +1 -0
  45. package/skills/init/assets/strategy_stub_template.md +38 -0
  46. package/skills/init/assets/workflow_template.json +1 -1
  47. package/skills/init/scripts/apply-project-visibility.sh +176 -0
  48. package/skills/init/scripts/detect-existing-codebase.sh +166 -0
  49. package/skills/init/scripts/init.sh +35 -1
  50. package/skills/jenga/SKILL.md +206 -14
  51. package/skills/jenga/scripts/board-scan.sh +238 -0
  52. package/skills/jenga/scripts/cascade-resolve.sh +297 -0
  53. package/skills/jenga/scripts/render-confirmation.sh +679 -0
  54. package/skills/jenga/scripts/render-picker.sh +439 -0
  55. package/skills/jenga/scripts/resolve-id.sh +367 -0
  56. package/skills/jenga-permission-level/SKILL.md +81 -0
  57. package/skills/proceed/SKILL.md +1 -1
  58. package/skills/publish/SKILL.md +8 -5
  59. package/skills/publish/assets/ci-contract.md +2 -2
  60. package/skills/publish/assets/ownership-matrix.md +1 -1
  61. package/skills/publish/scripts/finalize_changelog.sh +115 -0
  62. package/skills/publish/scripts/generate_release_notes.sh +475 -28
  63. package/skills/publish/scripts/npm_ci_pipeline.sh +44 -6
  64. package/skills/publish/scripts/publish_deploy.sh +38 -8
  65. package/skills/publish/scripts/run_gates.sh +2 -2
  66. package/skills/reconcile/SKILL.md +117 -5
  67. package/skills/reconcile/scripts/detect-unlinked-code.sh +741 -0
  68. package/skills/skillify/assets/init-new/assets/directory_structure.txt +5 -1
  69. package/skills/spinoff/SKILL.md +12 -7
  70. package/skills/todo/SKILL.md +2 -0
  71. package/skills/uncharted/SKILL.md +711 -0
  72. package/skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md +129 -0
  73. package/skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md +160 -0
  74. package/skills/uncharted/scripts/apply-subsystem-cap.sh +573 -0
  75. package/skills/uncharted/scripts/detect-dependencies.sh +732 -0
  76. package/skills/uncharted/scripts/detect-tests.sh +553 -0
  77. package/skills/uncharted/scripts/discover-subsystems.sh +1029 -0
  78. package/skills/uncharted/scripts/enumerate-target.sh +470 -0
  79. package/skills/uncharted/scripts/import-source.sh +517 -0
  80. package/skills/uncharted/scripts/inspect-provenance.sh +573 -0
  81. package/skills/uncharted/scripts/resolve-segment-target.sh +640 -0
  82. package/skills/uncharted/scripts/run-engine.sh +655 -0
  83. package/skills/uncharted/scripts/validate-proposed-items.sh +125 -0
  84. package/skills/uncharted/scripts/write-backfilled-epics.sh +498 -0
  85. package/skills/wtf/SKILL.md +20 -0
  86. package/templates/CHANGELOG_TEMPLATE.md +13 -0
  87. package/templates/PROBLEM_RAPPORT_TEMPLATE.md +4 -1
  88. package/templates/SCRUM_BOARD_SCHEMA.md +206 -10
  89. package/templates/permission-levels/README.md +73 -0
  90. package/templates/permission-levels/level-1-locked.json +71 -0
  91. package/templates/permission-levels/level-2-guarded.json +64 -0
  92. package/templates/permission-levels/level-3-standard.json +62 -0
  93. package/templates/permission-levels/level-4-elevated.json +60 -0
  94. package/templates/permission-levels/level-5-unrestricted.json +58 -0
  95. package/skills/convert/SKILL.md +0 -124
  96. package/skills/convert/convert_cli.py +0 -235
  97. package/skills/convert/tests/sample.csv +0 -4
  98. package/skills/convert/tests/sample.json +0 -5
  99. package/skills/convert/tests/sample.jsonl +0 -3
  100. package/skills/convert/tests/sample.yaml +0 -18
  101. package/skills/convert/tests/sample_obj.csv +0 -2
  102. package/skills/convert/tests/sample_obj.json +0 -9
  103. package/skills/mirror-public/SKILL.md +0 -237
  104. package/skills/mirror-public/assets/config.json +0 -5
  105. package/skills/mirror-public/scripts/mirror.sh +0 -374
  106. package/skills/self-sync/SKILL.md +0 -73
  107. package/skills/self-sync/scripts/run.js +0 -136
  108. package/skills/train/SKILL.md +0 -116
  109. package/skills/train/assets/dashboard-templates/classifiers.html +0 -106
  110. package/skills/train/assets/dashboard-templates/nlp.html +0 -102
  111. package/skills/train/assets/dashboard-templates/transformers.html +0 -98
  112. package/skills/train/assets/results-parsers/__init__.py +0 -9
  113. package/skills/train/assets/results-parsers/classifiers.py +0 -84
  114. package/skills/train/assets/results-parsers/nlp.py +0 -88
  115. package/skills/train/assets/results-parsers/reporter.py +0 -154
  116. package/skills/train/assets/results-parsers/transformers.py +0 -120
  117. package/skills/train/train_cli.py +0 -786
@@ -0,0 +1,711 @@
1
+ ---
2
+ name: uncharted
3
+ description: Investigate code that has no Jenga board provenance — a foreign file, an external source being pulled in, or an entire pre-existing codebase — and give it a consistent understanding document plus proper board representation.
4
+ metadata:
5
+ prefered_agent: scrum-master
6
+ keywords:
7
+ - "uncharted"
8
+ - "foreign code"
9
+ - "unfamiliar code"
10
+ - "existing codebase"
11
+ - "onboard codebase"
12
+ - "import source"
13
+ - "unlinked code"
14
+ - "no board history"
15
+ examples:
16
+ - "what does this file actually do?"
17
+ - "help me understand this directory I inherited"
18
+ - "we're adopting Jenga into an existing project, backfill the board"
19
+ - "pull in this repo and figure out how it fits"
20
+ - "this code has no board history, get it onto the board"
21
+ - "analyse the legacy module nobody understands"
22
+ - "onboard this existing codebase"
23
+ ---
24
+
25
+ # Uncharted — Investigative Workflow for Foreign & Pre-Existing Code
26
+
27
+ `/uncharted` is the entry point for code with **no board provenance** — code that was never planned, decomposed, or executed through the Jenga workflow.
28
+
29
+ It fills a gap the existing investigative skills leave open:
30
+
31
+ | Skill | Investigates | Requires |
32
+ |---|---|---|
33
+ | `/deep-dive` | ideas and proposals | no code target |
34
+ | `/improve` | the current codebase | an explicit goal |
35
+ | `/redo` | prior implementations | existing board history |
36
+ | **`/uncharted`** | **code with no board history** | **nothing but a target** |
37
+
38
+ Every mode runs the same shared investigative engine and emits the same understanding document. The modes differ only in **scale** and in **what happens after** the analysis.
39
+
40
+ ---
41
+
42
+ ## Invocation Contract
43
+
44
+ ```
45
+ /uncharted <mode> [target]
46
+ ```
47
+
48
+ | Mode | Scale | What it does | Implemented by |
49
+ |---|---|---|---|
50
+ | `segment` | one file, directory, or feature | Analyses a target that is already in the repo (or has just been imported), then proposes a standard epic/story/task so the segment gains normal board provenance. | E40_S02 |
51
+ | `import` | an external source | Acquires a git URL, an out-of-repo path, or a pasted snippet into the repo at a user-confirmed location, then hands off to `segment`. | E40_S03 |
52
+ | `onboard` | the whole codebase | Coarse-first pass over an existing project at framework-adoption time. Produces a capped set of **backfilled** epics. Board-only — never touches application code. | E40_S04 |
53
+
54
+ **Dispatch rules:**
55
+
56
+ 1. If the mode is one of `segment`, `import`, or `onboard`, dispatch to that section below.
57
+ 2. If the mode is missing or unrecognised, do **not** guess. Present the three modes as a numbered choice list with a free-text option last, per the Interaction Pattern in `CLAUDE.md`.
58
+ 3. If a mode is given but its target is missing, ask for the target the same way — a numbered list of plausible candidates where they can be inferred, free-text last.
59
+ 4. Never widen scope across modes in one invocation. `import` may hand off to `segment` because that handoff is part of its contract (E40_S03_T03); nothing else chains implicitly.
60
+
61
+ ---
62
+
63
+ ## Shared Investigative Engine
64
+
65
+ All three modes call the same engine rather than reimplementing analysis. Per the Skill Implementation Principle in `CLAUDE.md`, the deterministic work lives in scripts under `skills/uncharted/scripts/`, not in this file:
66
+
67
+ | Script | Responsibility | Implemented by |
68
+ |---|---|---|
69
+ | `enumerate-target.sh` | Resolves a target to file / directory / repo-root and emits its structure as JSON. Respects `.gitignore`; supports a `--max-depth` bound. | E40_S01_T02 |
70
+ | `detect-dependencies.sh` | Separates internal from external dependencies; lists manifests found in or above the target. | E40_S01_T03 |
71
+ | `detect-tests.sh` | Reports whether tests actually cover the target, distinguished from "the repo has tests but none reference it". | E40_S01_T03 |
72
+ | `run-engine.sh` | Mode-agnostic entry point. Invokes the three detectors above, merges their JSON, renders the understanding document, and prints the written path on stdout. | E40_S01_T04 |
73
+
74
+ **Invoke `run-engine.sh`, never the individual detectors.** It is the only supported entry point; calling a detector directly gets you raw JSON and no document.
75
+
76
+ ```bash
77
+ DOC=$(bash skills/uncharted/scripts/run-engine.sh --mode segment <target>)
78
+ ```
79
+
80
+ | Option | Effect |
81
+ |---|---|
82
+ | `--mode segment\|import\|onboard` | Mode hint. Default `segment`. Reflected in the filename and the document's Target section. `onboard` also defaults the tree depth to 2, because it is coarse-first by design. |
83
+ | `--max-depth N` | Tree depth bound (default 3, or 2 for `onboard`; `0` = unlimited). Bounds the **tree only** — file and line counts always cover the whole target. |
84
+ | `--top N` | How many largest-files rows to report. Default 10. |
85
+ | `--no-gitignore` | Enumerate `.gitignore`'d content too. Needed when `import` has staged a source into an ignored path. |
86
+ | `--origin "<text>"` | Value for the Target section's Origin row. `import` mode passes its acquired source here, e.g. `--origin "imported from https://…"`. Defaults to `in-repo`, or an explicit "outside this repository" when the target is. |
87
+ | `--out-dir <dir>` | Override the output directory. Default `project/rapports/analysis/`. |
88
+ | `--json-out <file>` | Also write the merged detector JSON, for when you want to reason over the raw evidence rather than the rendered prose. |
89
+ | `--template <file>` | Override the document template. Present for testing; production runs use the default. |
90
+
91
+ **Streams.** stdout is exactly one line — the absolute path of the written document — so `DOC=$(…)` is safe. Detector notices, skipped paths, and render warnings go to stderr. **Read the stderr.** It is where "4 hidden directories were skipped", "reference names truncated to the first 300", and "this target is not inside a git repository" show up, and each of those changes how much weight the document's findings deserve.
92
+
93
+ | Exit | Meaning | What to do |
94
+ |---|---|---|
95
+ | 0 | Document written | Proceed; the path is on stdout. |
96
+ | 1 | Usage error | Fix the invocation. Do not retry blindly. |
97
+ | 2 | Target does not exist or is unreadable | Re-resolve the target with the user; in `segment` mode this usually means the free-text description resolved to nothing. |
98
+ | 3 | A detector was missing or failed | Surface the forwarded stderr to the user. Do **not** hand-write a document to work around it. |
99
+ | 4 | Render or write failure | Template missing, `--out-dir` or `--json-out` directory unwritable, or the rendered document failed its seven-heading self-check. Report it; nothing is left behind — both destinations are checked before any detector runs, and the document is the last thing written. |
100
+
101
+ Do not restate in prose what these scripts do step by step, and do not reimplement their logic inline. Read the document (and `--json-out` when you need the raw evidence) and reason about it — that is the part that needs an agent.
102
+
103
+ > **Note:** the engine is live as of E40_S01. `segment` (E40_S02), `import` (E40_S03), and `onboard` (E40_S04) are all complete end to end — resolution or acquisition through to board write and handoff, in each mode's own terms.
104
+
105
+ ---
106
+
107
+ ## Output: the Understanding Document
108
+
109
+ Every mode produces exactly one document per target, rendered from `skills/uncharted/assets/UNDERSTANDING_DOC_TEMPLATE.md`.
110
+
111
+ **Where it goes:** `project/rapports/analysis/`, named `uncharted-<mode>-<slug>-<YYYYMMDDTHHMMSSZ>.md`. Nothing is ever overwritten — a name collision gets a `-2`, `-3`… suffix, so repeated runs against one target leave a diffable history.
112
+
113
+ This reuses the **existing `analysis` rapport type** already defined in `templates/SCRUM_BOARD_SCHEMA.md`. `/uncharted` introduces **no new rapport type** — if a change to the rapport taxonomy ever seems necessary, that is a schema change to be raised with the scrum-master, not something this skill invents.
114
+
115
+ **Fixed structure** — seven headings, never added to, removed, reordered, or renamed, because downstream steps read the document by heading:
116
+
117
+ `## Target` · `## Purpose` · `## Structure` · `## Key Dependencies` · `## Existing Tests` · `## Risk Areas` · `## Open Questions`
118
+
119
+ The engine enforces this itself: a render that would drop a heading exits 4 instead of writing. Diagnostics have nowhere to live in that structure, which is exactly why they go to stderr rather than being appended as an eighth section.
120
+
121
+ **Two classes of section, treated differently:**
122
+
123
+ | Section | Class | Filled by |
124
+ |---|---|---|
125
+ | `Target` | Mechanical | Engine — resolved path, target type, mode, board linkage, origin |
126
+ | `Structure` | Mechanical | Engine — file count, per-extension table, bounded tree, largest files |
127
+ | `Key Dependencies` | Mechanical | Engine — internal vs external, plus manifests found in or above the target |
128
+ | `Existing Tests` | Mechanical | Engine — coverage signal, referencing test files, runner configuration |
129
+ | `Purpose` | **Judgement** | **You**, from the mechanical evidence |
130
+ | `Risk Areas` | **Judgement** | **You**, from the mechanical evidence |
131
+ | `Open Questions` | **Judgement** | **You**, from the mechanical evidence |
132
+
133
+ The engine emits the three judgement sections as explicit `_TODO(agent):_` placeholders and must never fabricate them. After the engine runs:
134
+
135
+ 1. Read the document and the stderr notices.
136
+ 2. Replace each `_TODO(agent):_` line, citing what the mechanical sections actually found — a specific untested file, a named dependency, an oversized module.
137
+ 3. Where the evidence does not support a conclusion, say so under **Open Questions** and name who could answer it. That is a valid result, not a failure.
138
+ 4. Leave the headings alone.
139
+
140
+ Two signals deserve particular attention because they are easy to misread:
141
+
142
+ - **Coverage** is reported as `covered`, `repo_tests_only`, or `no_tests_in_repo`. "The repo has tests but none touch this target" is a finding about *this target*, not about the repo's testing culture — do not soften it into "the project has tests".
143
+ - **Board linkage** says `unlinked` when no board item mentions the target's path. That is the whole reason `/uncharted` exists; it is the starting condition, not a problem to report.
144
+
145
+ A document whose judgement sections are generic enough to apply to any codebase has failed its purpose. Ground every claim in something the scripts actually found.
146
+
147
+ ---
148
+
149
+ ## Modes
150
+
151
+ <!--
152
+ PLACEHOLDER SECTIONS — scaffolded by E40_S01_T01.
153
+
154
+ Each mode below is a self-contained block under a stable `### ` heading.
155
+ The owning task replaces the body of its own block and leaves the others
156
+ and the surrounding shared sections untouched.
157
+ -->
158
+
159
+ ### `segment`
160
+
161
+ Analyse a specific file, directory, or feature that has no board provenance, then give it standard board representation.
162
+
163
+ **Step 1 — Resolve the target.** Deterministic; do not eyeball it.
164
+
165
+ ```bash
166
+ bash skills/uncharted/scripts/resolve-segment-target.sh --json-only "<path-or-description>"
167
+ ```
168
+
169
+ It returns one `results[]` record. Read three fields:
170
+
171
+ | Field | Meaning |
172
+ |---|---|
173
+ | `kind` | `path` — the argument is an existing file or directory, already canonicalised in `target`. `description` — it is free text (exit 2). |
174
+ | `board_linkage.status` | `unlinked`, `linked`, or `not_checked` (with a `reason` — repo root, outside the repo, or no board directory). |
175
+ | `candidates[]` | Only for `kind: description`. Ranked path hints, capped by `--limit`. |
176
+
177
+ **When `kind` is `description`, you must ask.** Present `candidates[]` as a numbered choice list with a free-text option last, per the Interaction Pattern in `CLAUDE.md`. **Never** silently adopt the top-scored candidate — the score is a ranking hint, not a resolution, and picking for the user is how the wrong directory gets a whole epic written against it. An empty `candidates[]` is a normal result, not a failure: ask for the path directly. Exit 2 still prints its JSON, so read it rather than retrying.
178
+
179
+ **Report the linkage before doing anything else.** `unlinked` is the condition that justifies this whole workflow; say so. `linked` is a genuine finding — the target *already has* board provenance, so tell the user which items reference it and confirm they still want a segment pass rather than `/redo`. Do not proceed silently past a `linked` target.
180
+
181
+ > **Step 1 is authoritative on linkage.** `run-engine.sh` keeps its own private substring version for the document's `Board Linkage` row, and the two can disagree: a board item mentioning only `docs/hooks/guide.md` makes the document call `hooks/` *linked* while Step 1 correctly calls it *unlinked*. Where they differ, trust Step 1 and correct the row when you fill in the document — do not hand the user a document that contradicts what you just told them.
182
+ >
183
+ > The same script is also `/reconcile`'s board-linkage check (E40_S05_T02). Collapsing the two implementations into one is a deliberate follow-up, not something to do in passing — see the script's header.
184
+
185
+ **Step 2 — Run the engine** on the resolved absolute path:
186
+
187
+ ```bash
188
+ DOC=$(bash skills/uncharted/scripts/run-engine.sh --mode segment "<resolved target>")
189
+ ```
190
+
191
+ Options, streams, and exit codes are in **Shared Investigative Engine** above — do not restate them here, and do not reimplement enumeration, dependency, or test detection. Two mode-specific readings:
192
+
193
+ - **Exit 2 here means the resolution was wrong**, not that the engine is broken. Go back to Step 1 with the user.
194
+ - Pass `--origin` only when `import` handed the target over (E40_S03_T03). A plain in-repo segment derives its own origin.
195
+
196
+ Call the engine **once, unwrapped**. It writes `--json-out` before the document on purpose, so a failed JSON write never leaves an orphaned document behind; re-ordering or wrapping the call breaks that guarantee.
197
+
198
+ **Step 3 — Fill in the judgement sections** of the written document (`Purpose`, `Risk Areas`, `Open Questions`) per **Output: the Understanding Document** above, then give the user the document path.
199
+
200
+ **Step 4 — Choose the parent epic before proposing anything.** A segment rarely deserves an epic of its own.
201
+
202
+ List `project/board/epics/` and apply the normal scrum-master intake rule (`agents/scrum-master.md`): consider whether the segment belongs under an **existing** epic before creating a new one, and treat the standing **`Maintenance`** epic as the default home for chore-shaped work that belongs nowhere else. Three inputs decide it:
203
+
204
+ - **Step 1 said `linked`** — the epic of the item that already references the target is the default parent. Do not open a sibling epic for code that is already spoken for.
205
+ - **The document's `Purpose` matches an existing epic's Purpose** — that epic is the parent.
206
+ - **Neither** — `Maintenance` if the work is a chore; a new epic only when the segment is genuinely a body of work no existing epic covers.
207
+
208
+ A new epic is the last option, not the default, and needs a stated reason. Record the epics you weighed and rejected — "none fit" is a claim the user should be able to check.
209
+
210
+ **Step 5 — Draft the proposal** using `skills/uncharted/assets/SEGMENT_PROPOSAL_TEMPLATE.md`. Its six headings are fixed; fill every one of them. Two things it will not let you skip:
211
+
212
+ - **Every proposed task carries `execution_scope` and a non-empty `scope_rationale`** containing a file-count or line-count claim, assigned against `project/configs/scope-thresholds.json` rather than by feel — `inline` at or under 1 file / 20 lines, `story` when tasks share files across the story (up to 5 files), `task` otherwise. Never self-assign `execution_scope: epic`: it requires `epic_scope_approval: true`, which only a human sets. Propose `story` and say the task looks bigger instead. Field semantics live in `templates/SCRUM_BOARD_SCHEMA.md` — read them there, do not paraphrase them here.
213
+ - **The document's `Open Questions` carry over verbatim**, not re-derived, each with who can answer it and whether it blocks confirmation. A blocking question is resolved *before* the proposal is accepted, because the breakdown depends on its answer.
214
+ - **Propose everything the board file will contain**, not just titles. Each story's Acceptance Criteria and Definition of Done are proposed at the gate, and a new epic's Purpose and Definition of Done with it — `scripts/validate-story-format.sh` requires those sections, so anything missing here is text `E40_S02_T03` would have to invent after the user has already said yes.
215
+
216
+ Ground every story and task in a specific finding — the untested file, the named dependency, the oversized module. A breakdown that would fit any codebase has failed for the same reason a generic understanding document has.
217
+
218
+ **Step 6 — Stop at the confirmation gate.** Present the filled proposal in the session and offer the choice from its `## Decision` section, per the Interaction Pattern in `CLAUDE.md`. **The template's `## Decision` block is authoritative** — it is the presentation contract, and the copy below is a reading convenience. If the two ever disagree, the template wins and this passage is the one to fix:
219
+
220
+ 1. **Accept as proposed** — write these items to `project/board/`
221
+ 2. **Revise** — change the stories, tasks, or scopes before anything is written
222
+ 3. **Fit it under a different epic** — re-parent the proposal and show it again
223
+ 4. **Discard** — keep the understanding document, write no board items
224
+ 5. **Other (describe below)**
225
+
226
+ Option 1 is unavailable while any Open Question is marked as blocking. Resolve it first — the answer usually changes the breakdown — then re-present.
227
+
228
+ **Nothing under `project/board/` is created or modified before option 1 is chosen.** Not a stub epic, not a placeholder task, not an ID reservation, not "just the epic so the stories have somewhere to hang". Everything from Step 4 onward leaves `git status` unchanged — the only file this flow has written by now is the understanding document from Step 2, and that lives in `project/rapports/analysis/`.
229
+
230
+ Options 2 and 3 loop back to Step 5 (or to Step 4 for a re-parent) and re-present the proposal **in full**; nothing is written between rounds, because a half-written board is exactly the outcome this gate exists to prevent. Option 4 leaves the understanding document as the output — a legitimate result, not a failure. Silence, a counter-question, or an ambiguous reply is **not** consent; re-ask.
231
+
232
+ **Where this stops.** Resolution, linkage, the understanding document, and a *confirmed* proposal are the whole of Steps 1-6. Nothing reaches `project/board/` until option 1 is chosen; then Step 7 writes it.
233
+
234
+ **Step 7 — Write the accepted items to the board.** Only reachable from option 1, and only after any blocking Open Question is resolved.
235
+
236
+ The proposal is the input and the **only** source of content. Everything the board files contain — the epic's Purpose and Definition of Done, each story's `As a …, I want …, so that …` line, Acceptance Criteria, and Definition of Done, each task's `execution_scope` and `scope_rationale` — was proposed at the gate and is transcribed **verbatim**. Nothing is composed, expanded, or improved at write time. If a field looks wrong now, that is a revision (option 2), not an edit in passing: text the user did not read has no business on the board, which is the whole point of the gate.
237
+
238
+ Shape, field names, and file naming come from `templates/SCRUM_BOARD_SCHEMA.md`. Read them there — do not paraphrase them here. Five things it will not let you skip:
239
+
240
+ - **Parent before child.** Epic (if new), then stories, then tasks. The schema's Linking Convention makes the epic's `stories[]` and each story's `tasks[]` the authoritative index, so a child written before its parent leaves an index nobody has updated.
241
+ - **IDs continue the sequence they belong to.** `E##` is the next free epic number board-wide, but `S##` is the next free story number **within its parent epic** and `T##` the next free task number **within its parent story** — that is what the `E##_S##_T##` shape means. Story numbers restarting per epic is normal and expected, not a collision. Nothing catches an error here: `validate-board.sh` checks an ID's *shape*, never its uniqueness or its sequence, so a well-formed wrong number passes every gate in Step 7 and quietly reuses an ID that already meant something else.
242
+ - **Take the advisory lock** described under the schema's File Locking section before each write, and release it with a `trap` so a failed write does not leave a `.lock` behind that blocks the next agent for good.
243
+ - **`status: Pending`, `date_created` today, `date_started` empty.** These items have not been executed. Only the tester moves a status off `Pending` — do not write anything else, however confident the proposal was.
244
+ - **`jenga_assigned: true`** for scopes assigned at Step 5 against `project/configs/scope-thresholds.json`. If the user changed a scope during a Revise round, that scope was a human override: `jenga_assigned: false` **with** an `override_justification` saying what they changed and why. Never set `epic_scope_approval` — only a human sets that.
245
+
246
+ Then prove it mechanically rather than asserting it:
247
+
248
+ ```bash
249
+ bash skills/uncharted/scripts/validate-proposed-items.sh <every file just written>
250
+ ```
251
+
252
+ It runs `scripts/validate-board.sh` over every file and `scripts/validate-story-format.sh` over every story file, and exits non-zero listing **each** failure — not just the first, because deciding whether to repair or roll back needs the whole list. Exit 2 means a usage error or a missing validator, not a bad board file.
253
+
254
+ **A non-zero exit leaves you mid-write, and that is the one state this flow must not end in.** Repair the named files and re-run, or delete everything written in this step and return to Step 5 with the failures. Deleting is safe here and only here: every one of these files is new, so there is nothing to restore. Never report success, and never hand off to Step 8, on a board that has not passed.
255
+
256
+ On success, tell the user exactly which files were created, with their IDs.
257
+
258
+ **Step 8 — Hand off to the standard path.** Queue each new task with the canonical todo owner — `scripts/todo_manager.sh add "<entry>"`, one call per task, referencing the task ID — and the work then proceeds through the ordinary `/todo` → `/do` → developer → tester path, with no special casing anywhere along it. Point the tasks' Description at the understanding document from Step 2; it is the context the developer picking one up would otherwise lack.
259
+
260
+ **`/uncharted` writes board files and stops there.** It does not adapt the segment to project conventions, edit or move the code it just analysed, open a worktree, or write an execution plan. That is ordinary developer work, driven by ordinary task files, and it is the developer agent's job — the same as for a task that came from `/brainstorm` or `/pi-plan`. A segment that has reached the board is no longer a special case, and this skill growing its own integration path would be a second, divergent execution route for work the existing one already handles.
261
+
262
+ ### `import`
263
+
264
+ Pull an external source into the repo, then investigate it as a segment.
265
+
266
+ > **Status: `import` is complete end to end (E40_S03_T01–T03).** Acquisition, provenance
267
+ > surfacing and placement confirmation, and the move-and-handoff into `segment` are all live.
268
+ > `import` contributes exactly two things `segment` does not — acquisition (Steps 1-2) and
269
+ > placement (Steps 3-6) — then Step 7 hands off into `segment`'s own flow for everything else.
270
+ > No enumeration, dependency-detection, or test-detection logic is duplicated here; that all
271
+ > lives in `segment` and the shared engine.
272
+
273
+ **Step 1 — Acquire into staging.** Deterministic; do not hand-roll a clone or a copy.
274
+
275
+ ```bash
276
+ SUMMARY_JSON=$(bash skills/uncharted/scripts/import-source.sh --json-only "<source>")
277
+ ```
278
+
279
+ `<source>` is a git URL, a local path **outside** this repo, or `-` (with the snippet piped
280
+ on stdin, `--type snippet --name <filename>`). Read four fields off `SUMMARY_JSON`:
281
+ `source_type`, `source`, `staging_path`, `content_path`, and `git_ref` (the short HEAD SHA,
282
+ present only for a `git`-type acquisition — `import-source.sh` deletes the clone's `.git`
283
+ directory immediately after reading it, as part of treating acquired content as hostile, so
284
+ this is the **only** point at which that SHA is ever recoverable). Exit codes and the full
285
+ security contract (no hooks, no install scripts, staging always outside the repo working
286
+ tree) are documented in the script's own header — read them there, do not restate them here.
287
+
288
+ **A non-zero exit here means nothing was acquired.** Surface the script's stderr to the user
289
+ and stop; there is no staging directory to clean up in that case (the script cleans up after
290
+ itself on failure).
291
+
292
+ **Step 2 — Inspect provenance.** Also deterministic, and mandatory before Step 3 — the
293
+ destination proposal is presented *alongside* these findings, never before them.
294
+
295
+ ```bash
296
+ PROVENANCE_JSON=$(bash skills/uncharted/scripts/inspect-provenance.sh \
297
+ --origin-source "$(echo "$SUMMARY_JSON" | jq -r .source)" \
298
+ --origin-type "$(echo "$SUMMARY_JSON" | jq -r .source_type)" \
299
+ --origin-ref "$(echo "$SUMMARY_JSON" | jq -r '.git_ref // ""')" \
300
+ "$(echo "$SUMMARY_JSON" | jq -r .content_path)")
301
+ ```
302
+
303
+ Forward `source` / `source_type` / `git_ref` from Step 1's summary exactly like this — do not
304
+ omit the `--origin-*` flags because they look redundant with what's already on disk.
305
+ `inspect-provenance.sh` cannot recover them itself: acquisition already deleted the `.git`
306
+ directory that held them. What it *does* find live on disk is reported independently: any
307
+ LICENSE/LICENCE/COPYING/UNLICENSE/NOTICE file (with a best-effort type classification), any
308
+ `SPDX-License-Identifier` headers, copyright lines near the top of files, and any **nested**
309
+ `.git` directory the acquisition didn't strip (a vendored checkout, for instance).
310
+
311
+ **Read every section's `message` field, not just `found`.** `inspect-provenance.sh` states
312
+ explicitly, per category, when nothing was found — `license.message`, `spdx_identifiers.message`,
313
+ `copyright.message`, `git_origin.message` are all non-null exactly when that section is empty —
314
+ and `overall.message` restates it plainly when *every* section comes back empty. **Never
315
+ translate "no license file was found" into "this source is unencumbered" or anything implying
316
+ it.** This is an informational surface, not a legal clearance: state what was found, state
317
+ plainly when nothing was, and let the user decide. The script's own stderr also carries
318
+ scan-scope notices (directories excluded from the content scan, symlinked files that were
319
+ found but never opened for safety, a `--max-files` cap being hit) — read them, the same as any
320
+ other engine script's stderr.
321
+
322
+ **Step 3 — Propose a destination.** Judgement, not a script: derive a short slug from the
323
+ source (the repo name, the snippet's `--name`, the last path component of a local source) and
324
+ propose a location under the repo that fits where similar content already lives — alongside a
325
+ related existing area when one is evident from the source's contents, or a clearly-named new
326
+ top-level location when nothing existing fits. State *why* briefly; a bare path with no
327
+ rationale gives the user nothing to evaluate at Step 4.
328
+
329
+ **Step 4 — Confirm before anything leaves staging.** Present the proposed destination
330
+ together with the Step 2 findings, then require an explicit numbered choice, per the
331
+ Interaction Pattern in `CLAUDE.md` — free-text last:
332
+
333
+ ```
334
+ Provenance findings for <source>:
335
+ <Step 2's findings, summarised — license/SPDX/copyright status, and the overall message
336
+ verbatim when nothing was found>
337
+
338
+ Proposed destination: <path>
339
+ <one-line rationale>
340
+
341
+ How should this be placed?
342
+ 1. Use the proposed path
343
+ 2. Use a different path (name it)
344
+ 3. Abort the import
345
+ 4. Other (describe below)
346
+ ```
347
+
348
+ **Silence, a counter-question, or an ambiguous reply is not consent** — re-ask, the same
349
+ convention `segment`'s Step 6 uses. Option 2 loops back to Step 3 with the user's named path in
350
+ place of the derived one and re-presents this same confirmation; it does not silently accept a
351
+ path that was never shown back to the user for approval.
352
+
353
+ **Nothing under the repo working tree is written by reaching this gate.** Content stays in
354
+ `content_path`, exactly where Step 1 left it, until the user picks option 1 or a confirmed
355
+ option 2 path.
356
+
357
+ **Step 5 — Abort leaves no trace.** Option 3 removes the entire staging root (not just
358
+ `content_path`) —
359
+
360
+ ```bash
361
+ rm -rf "$(echo "$SUMMARY_JSON" | jq -r .staging_path)"
362
+ ```
363
+
364
+ — and stops. Confirm with `git status` that the working tree is unchanged; nothing from this
365
+ flow was ever written under it, so there is nothing to revert, only the staging directory to
366
+ discard.
367
+
368
+ **Step 6 — Move into place.** Reachable only from a confirmed destination: option 1 at Step 4, or
369
+ a confirmed option 2 path. This is a plain filesystem operation — `import-source.sh` staged
370
+ content, it does not place it, and there is no dedicated placement script because the semantics
371
+ are simple and specific to this one call site:
372
+
373
+ ```bash
374
+ DESTINATION="<confirmed path, repo-relative>"
375
+ CONTENT_PATH="$(echo "$SUMMARY_JSON" | jq -r .content_path)"
376
+ STAGING_PATH="$(echo "$SUMMARY_JSON" | jq -r .staging_path)"
377
+
378
+ if [ -e "$DESTINATION" ] && [ -n "$(ls -A "$DESTINATION" 2>/dev/null)" ]; then
379
+ echo "Destination already exists and is not empty — this would overwrite existing content." >&2
380
+ # Stop. Go back to Step 4 with a different path; do not merge into or overwrite it.
381
+ else
382
+ mkdir -p "$DESTINATION"
383
+ cp -R "$CONTENT_PATH/." "$DESTINATION/"
384
+ rm -rf "$STAGING_PATH"
385
+ fi
386
+ ```
387
+
388
+ `content_path`'s contents land **directly under** the destination directory — never nested
389
+ inside an extra subdirectory — whether `content_path` holds a whole tree (a cloned repo, an
390
+ imported directory) or a single file (a snippet, a single imported file). The staging root is
391
+ removed afterward the same way Step 5 removes it on abort, but for the opposite reason: the
392
+ content now has a permanent home, not because it is being discarded.
393
+
394
+ **Refuse to overwrite.** A destination that already exists and is non-empty is not merged into
395
+ or clobbered — stop and return to Step 4 for a different path. This is the same "never silently
396
+ written into an arbitrary path" guarantee the confirmation gate exists to uphold; it applies just
397
+ as much to what is already there as to what the user did not confirm.
398
+
399
+ The placed files are new, uncommitted content in the working tree at this point — `/uncharted`
400
+ places them and stops; it does not commit. Committing them is ordinary work for whichever task
401
+ Step 7 below eventually queues, the same as any other new file a task depends on.
402
+
403
+ **Step 7 — Hand off to `segment`.** The source is now at its final in-repo path. From here,
404
+ `import` contributes nothing further: everything below is `segment` mode's own flow (above), run
405
+ against the destination, not a second implementation of it.
406
+
407
+ - Run **segment's Step 1** (`resolve-segment-target.sh`) against `$DESTINATION` for its
408
+ board-linkage status. A freshly placed path is normally `unlinked`, but check rather than
409
+ assume — a board item could already reference the intended path from prior planning.
410
+ - Run **segment's Step 2** (`run-engine.sh --mode segment "$DESTINATION" --origin "imported from
411
+ $(echo "$SUMMARY_JSON" | jq -r .source)"`) — exactly the case segment's own Step 2 note
412
+ anticipates ("Pass `--origin` only when `import` handed the target over").
413
+ - **At segment's Step 3 (filling the judgement sections), carry the provenance findings from
414
+ this section's Step 2 (`PROVENANCE_JSON`) into `Risk Areas` — do not re-derive or rediscover
415
+ them:**
416
+ - Read `PROVENANCE_JSON`'s `license`, `spdx_identifiers`, `copyright`, and `git_origin`
417
+ sections, plus `overall.message`, and fold every finding — and the fact of an empty one —
418
+ into `Risk Areas`, grounded exactly as segment's Step 3 already requires elsewhere: a
419
+ specific license file (or its stated absence), a specific SPDX identifier, a specific
420
+ copyright line, a nested `.git` left over from a vendored checkout.
421
+ - An unclear or absent license is itself a Risk Area entry. Word it the same cautious way
422
+ `inspect-provenance.sh`'s own `overall.message` already does — the scan found nothing, which
423
+ is not the same claim as the source being unencumbered — and never soften it into "no
424
+ licensing concerns".
425
+ - Where the licensing question needs a human answer before the segment is safely integrated
426
+ (no license file, an unrecognised SPDX identifier, an un-vetted nested `.git`), also add it
427
+ as an `Open Questions` entry, marked `Blocks confirmation? yes` when it is serious enough to
428
+ hold up acceptance. Segment's Step 5 already carries `Open Questions` into the proposal
429
+ **verbatim** — recording it once here is the entire mechanism by which a provenance finding
430
+ reaches the board proposal. There is no separate provenance field on the proposal template
431
+ and none is needed.
432
+ - Continue through **segment's Steps 4-8** exactly as written above: choose the parent epic,
433
+ draft the proposal, stop at the confirmation gate, and — only on acceptance — write the board
434
+ and queue the tasks. Do not skip Step 6's confirmation gate because this section's Step 4
435
+ already asked a question; that gate confirmed *placement*, segment's Step 6 confirms the
436
+ *board breakdown*, and they are different decisions the user must see separately.
437
+
438
+ Nothing about enumeration, dependency detection, or test detection is re-specified here —
439
+ segment's own Step 2 (`run-engine.sh`) already does all of that, once, against the placed
440
+ destination.
441
+
442
+ ### `onboard`
443
+
444
+ Backfill the board for an entire pre-existing codebase at framework-adoption time.
445
+
446
+ > **Placeholder — implemented by E40_S04** (`E40_S04_T01` `provenance` field on epics, `E40_S04_T02` subsystem discovery, `E40_S04_T03` cap enforcement with drop logging, `E40_S04_T04` backfilled epic generation, `E40_S04_T05` `PROJECT_SUMMARY.md` population). All five are live.
447
+ >
448
+ > Contract this section must honour when filled in:
449
+ > - Coarse-first: identifies major subsystems, not individual files. Depth is bounded, not exhaustive.
450
+ > - Emits a **capped** number of epics, each marked `provenance: backfilled` so they are distinguishable from normally-authored epics.
451
+ > - The cap is explicit and anything dropped to stay under it is reported to the user. No silent truncation.
452
+ > - **Board and documentation only.** Never modifies, moves, or restructures the consumer's application code. Jenga lives alongside the app; the app never has to conform to a Jenga convention.
453
+
454
+ #### The hard constraint: `onboard` never touches application code
455
+
456
+ **`onboard` mode must never modify, move, rename, delete, or restructure any file that belongs to
457
+ the consumer's application.** This is not a best-effort convention — it is the reason `onboard`
458
+ exists as a *board-only* mode rather than a generic migration tool. Its entire output surface,
459
+ without exception, is:
460
+
461
+ - `project/board/` (the backfilled epic files this section produces)
462
+ - `project/rapports/analysis/` (understanding documents and the subsystem cap record)
463
+ - `project/PROJECT_SUMMARY.md` (populated by `E40_S04_T05`)
464
+
465
+ Nothing under any other path is ever created, edited, or deleted by `onboard` mode. Jenga's board
466
+ and skills live *alongside* the application; the application never has to conform to a Jenga
467
+ convention to be onboarded. This guarantee is enforced in three independent places, not just
468
+ stated here:
469
+
470
+ 1. **This prohibition**, read by any agent driving `onboard` mode.
471
+ 2. **A structural write-path guard** in `skills/uncharted/scripts/write-backfilled-epics.sh` — its
472
+ `--epics-dir` and `--json-out` destinations are canonicalised and checked against
473
+ `<repo-root>/project/` *before* any file is written, with no flag able to point either one
474
+ outside it. A path that resolves outside `project/` is a hard failure (exit 3), nothing
475
+ written — not a warning that can be missed.
476
+ 3. **A verifiable post-run check** (below): after a full `onboard` run, `git status` in the
477
+ analysed repository must show changes only under `project/`.
478
+
479
+ #### The subsystem cap
480
+
481
+ Live as of `E40_S04_T03`. `onboard` does **not** create one epic per discovered subsystem — a
482
+ 40-subsystem repository would bury the board on day one. It caps how many become epics, and
483
+ `skills/uncharted/scripts/apply-subsystem-cap.sh` applies that cap to `discover-subsystems.sh`'s
484
+ ranked output:
485
+
486
+ ```bash
487
+ bash skills/uncharted/scripts/discover-subsystems.sh <root> \
488
+ | bash skills/uncharted/scripts/apply-subsystem-cap.sh --rapport "$DOC"
489
+ ```
490
+
491
+ It emits JSON with two explicit arrays — `kept` (the subsystems that become backfilled epics) and
492
+ `dropped` (everything below the cap, each entry carrying its path, rank, score and the reason
493
+ string `below cap of N`).
494
+
495
+ | Option | Effect |
496
+ |---|---|
497
+ | `--cap N` | **The cap. Default 8.** Raise it to backfill more subsystems. Must be `1` or higher — `--cap 0` or a negative cap exits non-zero, because "produce no epics at all" is never a meaningful onboard run. |
498
+ | `--rapport <file>` | Append the cap record to the onboard analysis document `run-engine.sh --mode onboard` already wrote. This is the normal flow: the cap record belongs with the analysis that produced it. |
499
+ | `--out-dir <dir>` | Where a standalone cap record goes when `--rapport` is not given. Default `project/rapports/analysis/`. |
500
+ | `--json-out <file>` | Also write the report to a file. stdout gets it either way. |
501
+ | `--label "<text>"` | Human label for the codebase in the record. Defaults to the analysed root. |
502
+
503
+ **Dropped subsystems are always surfaced, and are never silently truncated.** Every run writes a
504
+ human-readable `## Subsystem Cap` section into an analysis rapport under
505
+ `project/rapports/analysis/` naming **every** dropped subsystem individually, with its rank, score
506
+ and reason. That record is the point of the script, not a side effect of it: a user who onboards a
507
+ 40-subsystem repo and gets 8 epics must be able to find out, later and on disk, which 32 were left
508
+ off and why — long after the chat message saying so has scrolled away. There is deliberately no
509
+ flag to skip that write, no elision in the list, and an unwritable rapport fails the whole run
510
+ rather than returning a clean result with no audit trail.
511
+
512
+ Tell the user the drop count and the rapport path. Do not paste the whole dropped list into the
513
+ conversation — it is on disk, in full, which is exactly the durability being bought here.
514
+
515
+ Nothing is dropped when the candidate count is at or below the cap: `dropped` is an empty array
516
+ and the run still exits 0. A dropped subsystem is not a rejected one — it simply has no backfilled
517
+ epic, and can be brought onto the board individually later with `/uncharted segment <path>`, or by
518
+ re-running `onboard` with a higher `--cap`.
519
+
520
+ | Exit | Meaning |
521
+ |---|---|
522
+ | 0 | Cap applied; record written (including the nothing-was-dropped case). |
523
+ | 1 | Usage error — unknown flag, non-integer cap, or a cap of 0 or below. |
524
+ | 2 | Input error — the report is missing, unreadable, not JSON, or has no `candidates` array. |
525
+ | 4 | The drop record could not be written. Fatal by design. |
526
+
527
+ #### Generating the backfilled epics
528
+
529
+ Live as of `E40_S04_T04`. `skills/uncharted/scripts/write-backfilled-epics.sh` is the only thing
530
+ that actually writes backfilled epics to the board — it consumes `apply-subsystem-cap.sh`'s
531
+ `kept` array and renders one epic file per entry, following the Epic format in
532
+ `templates/SCRUM_BOARD_SCHEMA.md` exactly (`provenance: backfilled`, `status: Pending`, an empty
533
+ `stories` array, a Purpose section built from the discovery evidence, and a Definition of Done
534
+ framed around understanding and integration, never original construction):
535
+
536
+ ```bash
537
+ bash skills/uncharted/scripts/discover-subsystems.sh <root> \
538
+ | bash skills/uncharted/scripts/apply-subsystem-cap.sh --rapport "$DOC" \
539
+ | bash skills/uncharted/scripts/write-backfilled-epics.sh
540
+ ```
541
+
542
+ | Option | Effect |
543
+ |---|---|
544
+ | `--epics-dir <dir>` | Where epic files are written. Default `<repo-root>/project/board/epics`. Must resolve under `<repo-root>/project/` — see the write-path guard above. |
545
+ | `--json-out <file>` | Also write this script's JSON summary to a file. Same guard applies. |
546
+ | `--dry-run` | Compute IDs and render content without writing anything — useful for previewing what a run would produce. |
547
+ | `--label "<text>"` | Human label for the analysed codebase, used in each epic's Purpose section. |
548
+
549
+ **Epic ID continuation.** The next free `E##` is one past the highest epic number found under
550
+ both `--epics-dir` and the canonical `project/board/epics/`, so generated epics always continue
551
+ the existing board's numbering and never collide with an epic already on it.
552
+
553
+ **Evidence honesty.** `apply-subsystem-cap.sh`'s `kept` entries carry only `path`, `rank`,
554
+ `score`, `files`, and `lines` — not the richer per-candidate signals (`manifests`, `test_paths`,
555
+ `doc_paths`, `signals`) that `discover-subsystems.sh` computes internally but does not pass
556
+ through the cap step. Each generated epic's Purpose section says exactly this, and points at
557
+ `/uncharted segment <path>` for a full understanding document before the epic is broken into
558
+ stories. Never fabricate deeper Purpose detail than the `kept` entry actually supports.
559
+
560
+ **Verifying the read-only guarantee.** After a full `onboard` run (discovery → cap → epic
561
+ generation) against a repository containing application code, confirm the guarantee held:
562
+
563
+ ```bash
564
+ git status --porcelain
565
+ ```
566
+
567
+ Every line must start with a path under `project/`. Any other path in that output is a defect —
568
+ `onboard` mode has no legitimate reason to touch it — and the run should be treated as failed
569
+ even if every script above exited 0.
570
+
571
+ `git status --porcelain` has a blind spot: git does not track empty directories, so a run that
572
+ creates (and leaves behind) an empty directory outside `project/` — for example from a
573
+ misconfigured or typo'd `--epics-dir`/`--json-out` — would pass this check while still having
574
+ violated the guarantee. `write-backfilled-epics.sh`'s write-path guard resolves its target paths
575
+ before creating anything, specifically so this case cannot arise from a rejected path, but when
576
+ verifying by hand it is worth also diffing the raw filesystem tree, which has no such blind spot:
577
+
578
+ ```bash
579
+ find . -mindepth 1 ! -path './project/*' ! -path './project' -newer <timestamp-file-from-before-the-run>
580
+ ```
581
+
582
+ An empty result confirms nothing outside `project/` was even touched, not merely that nothing
583
+ outside `project/` ended up tracked by git.
584
+
585
+ | Exit | Meaning |
586
+ |---|---|
587
+ | 0 | Epics written (including a `kept` array of length zero: 0 epics is a valid outcome). |
588
+ | 1 | Usage error — unknown flag, missing value. |
589
+ | 2 | Input error — the report is missing, unreadable, not JSON, or has no `kept` array. |
590
+ | 3 | Write-path guard — `--epics-dir` or `--json-out` resolved outside `<repo-root>/project/`. |
591
+ | 4 | Write failure — an epic file could not be written, or an existing epic file could not be read while computing ID continuation. |
592
+
593
+ #### Updating PROJECT_SUMMARY.md from onboard evidence
594
+
595
+ Live as of `E40_S04_T05`. This is the last step of an `onboard` run, after the backfilled epics
596
+ above have been written (or confirmed skipped). Its job is to close the gap the epic itself
597
+ describes: a project adopting Jenga should not end up with a populated board sitting underneath a
598
+ `PROJECT_SUMMARY.md` that still reads as if the codebase were empty.
599
+
600
+ **This is a scrum-master action, not a script.** Nothing under `skills/uncharted/scripts/` writes
601
+ `PROJECT_SUMMARY.md`, and the developer agent never touches it either — both `agents/developer.md`
602
+ and `agents/scrum-master.md` already establish `PROJECT_SUMMARY.md` as scrum-master-owned, sole
603
+ writer, no exceptions, and `onboard` does not get to be the exception just because the content
604
+ originated from an automated discovery pass. Whichever agent is driving `onboard` performs the
605
+ scripted steps above itself, but for this step it hands the drafting and the write to the
606
+ scrum-master rather than doing it inline.
607
+
608
+ **Inputs — reuse the evidence already produced, derive nothing new:**
609
+
610
+ - The onboard understanding document(s) under `project/rapports/analysis/` written by
611
+ `run-engine.sh --mode onboard` (in particular their `Purpose` and `Structure` sections).
612
+ - The `kept` array from `apply-subsystem-cap.sh` — the same subsystems that became backfilled
613
+ epics, with the same `path`, `rank`, `score`, `files`, and `lines` fields described under
614
+ **Evidence honesty** above. Do not re-run discovery or invent detail those entries don't carry;
615
+ if the epics' Purpose sections had to point at `/uncharted segment <path>` for deeper detail,
616
+ the summary draft is bound by the same limit.
617
+
618
+ **Step A — Draft, don't write yet.** From the inputs above, draft replacement text for two
619
+ sections of `project/PROJECT_SUMMARY.md`:
620
+
621
+ - **Overview** — what the project is, in the terms the onboard evidence actually supports (what
622
+ it does, for whom, at what scale), not a generic description that would fit any codebase.
623
+ - **Architecture & Structure** — one entry per **kept** subsystem: what it is and how it relates
624
+ to the others (depends on, is depended on by, sits alongside). This is prose about
625
+ responsibilities and relationships, not a listing of file paths. A draft that reads as "`src/`,
626
+ `lib/`, `api/`" with no relationship stated has failed this step, even if every path is correct.
627
+
628
+ **Step B — Check both sections for existing content before proposing anything.** A section counts
629
+ as a **stub** only if its body is empty, whitespace-only, or is (or is limited to) the literal
630
+ placeholder text from `skills/init/assets/PROJECT_SUMMARY_template.md` — `_To be completed._`.
631
+ Anything else — a sentence, a partial list, a paragraph someone already wrote by hand — is real,
632
+ non-stub content, however short, and is never silently overwritten. Check the Overview and
633
+ Architecture & Structure sections independently; one can be a stub while the other is not.
634
+
635
+ **Step C — Present for confirmation, every time, stub or not.** Consistent with the
636
+ confirm-before-writing convention `segment` mode already uses at its Step 6, and with **Confirm
637
+ before writing to the board** under Constraints below (which this step extends explicitly to
638
+ `PROJECT_SUMMARY.md`, since that file sits outside `project/board/`): show the drafted Overview and
639
+ Architecture & Structure text in the session before writing anything. What is offered depends on
640
+ what Step B found:
641
+
642
+ - **Both sections are stubs.** Show the draft and ask for a straight go/no-go:
643
+
644
+ ```
645
+ Ready to populate PROJECT_SUMMARY.md's Overview and Architecture & Structure from the onboard
646
+ evidence?
647
+ 1. Write it as drafted
648
+ 2. Revise — change the draft before writing
649
+ 3. Skip — leave PROJECT_SUMMARY.md untouched
650
+ 4. Other (describe below)
651
+ ```
652
+
653
+ - **Either section has real, non-stub content.** Never overwrite it by default. Present the
654
+ numbered choice from the task's Acceptance Criteria, per the Interaction Pattern in `CLAUDE.md`:
655
+
656
+ ```
657
+ PROJECT_SUMMARY.md's <Overview and/or Architecture & Structure> section already has content.
658
+ How should the onboard findings be applied?
659
+ 1. Merge — integrate the onboard findings into the existing text, keeping what's there
660
+ 2. Replace — overwrite the existing section with the onboard draft
661
+ 3. Skip — leave this section untouched
662
+ 4. Other (describe below)
663
+ ```
664
+
665
+ Ask this once per affected section if only one of the two is non-stub, or once covering both if
666
+ both are. **Replace is the only option that removes existing prose.** Merge means the
667
+ scrum-master integrates the two — existing text stays, onboard findings are woven in or appended
668
+ — never a silent supersede dressed up as a merge.
669
+
670
+ Silence, a counter-question, or an ambiguous reply is not consent, the same as at `segment`'s
671
+ Step 6 — re-ask rather than proceeding.
672
+
673
+ **Step D — Write, scoped to exactly what was confirmed.** Only the scrum-master writes, only to
674
+ `project/PROJECT_SUMMARY.md`, and only the section(s) and content actually confirmed in Step C —
675
+ a "skip" on one section must not carry an incidental edit to it from a "replace" on the other.
676
+ Like epic generation above it, "skip" on both sections is a valid outcome, not a failure: a run
677
+ that leaves `PROJECT_SUMMARY.md` untouched still respects the read/write surface named under **The
678
+ hard constraint** above, since that file was never a required write, only a permitted one.
679
+
680
+ ---
681
+
682
+ ## Constraints
683
+
684
+ These hold across every mode:
685
+
686
+ - **Read-only against application code**, with one exception: `import` mode writes an acquired source to a location the user has explicitly confirmed. Nothing else in this skill modifies, moves, or restructures a consumer's code.
687
+ - **Confirm before writing to the board.** Proposals are presented for approval first, consistent with the brainstorm-before-commit convention.
688
+ - **No new rapport type.** Understanding documents use the existing `analysis` type and land in `project/rapports/analysis/`.
689
+ - **Never invent findings.** If the evidence does not support a conclusion, say so under Open Questions.
690
+ - **Deterministic work belongs in `skills/uncharted/scripts/`**, agent judgement belongs here.
691
+
692
+ ---
693
+
694
+ ## Entry Points
695
+
696
+ `/uncharted` is invocable directly, and is also offered automatically at the two moments it is most needed (E40_S05):
697
+
698
+ - **`/init`** — when scaffolding detects a non-empty, non-Jenga-scaffolded directory, it offers `onboard` instead of proceeding as though the project were empty.
699
+ - **`/reconcile`** — when its normal sync pass finds code with no board linkage, it offers `segment` for the affected paths.
700
+
701
+ Both are offers, never automatic execution.
702
+
703
+ ---
704
+
705
+ ## Session End
706
+
707
+ When this skill's session concludes, emit the following signal on its own line so the Jenga Router clears the active session:
708
+
709
+ ```
710
+ [JENGA:SESSION_END:uncharted]
711
+ ```