@jenga-ai/agent 3.6.0 → 4.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.
- package/lib/generate-skill-allow-list.js +42 -5
- package/lib/skill-allow-list.json +1 -1
- package/package.json +1 -2
- package/project/app/api/lib/resolve-project-root.js +1 -1
- package/project/app/package.json +4 -0
- package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
- package/project/app/ui/dist/assets/index-C3oiuli_.js +104 -0
- package/project/app/ui/dist/index.html +2 -2
- package/scripts/acquire-concurrency-slot.sh +34 -4
- package/scripts/apply-j-prefix.sh +1 -1
- package/scripts/delete-bare-skill-dirs.sh +1 -2
- package/scripts/idea_manager.sh +16 -1
- package/scripts/release-concurrency-slot.sh +34 -4
- package/scripts/render-ranked-list.sh +270 -0
- package/scripts/repoint-dead-bare-path-prose.py +81 -0
- package/scripts/rewrite-stale-skill-preambles.py +188 -0
- package/scripts/strip-polyfill-frontmatter.py +166 -0
- package/scripts/todo_manager.sh +16 -1
- package/scripts/validate-typed-object.sh +750 -0
- package/skills/j-brainstorm/SKILL.md +3 -4
- package/skills/j-btw/SKILL.md +3 -4
- package/skills/j-clearify/SKILL.md +3 -4
- package/skills/j-close-story/SKILL.md +11 -12
- package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
- package/skills/j-commit/SKILL.md +3 -4
- package/skills/j-continue/SKILL.md +5 -6
- package/skills/j-deep-dive/SKILL.md +3 -4
- package/skills/j-distribute/SKILL.md +3 -4
- package/skills/j-do/SKILL.md +13 -15
- package/skills/j-doc/SKILL.md +3 -4
- package/skills/j-doc-sync/SKILL.md +3 -4
- package/skills/j-dooo/SKILL.md +6 -15
- package/skills/j-error/SKILL.md +3 -4
- package/skills/j-evaluate/SKILL.md +3 -4
- package/skills/j-examplify/SKILL.md +3 -4
- package/skills/j-help/SKILL.md +3 -4
- package/skills/j-idea/SKILL.md +3 -4
- package/skills/j-improve/SKILL.md +3 -4
- package/skills/j-init/SKILL.md +20 -11
- package/skills/j-init/scripts/apply-scaffold-visibility.sh +8 -6
- package/skills/j-jbp/SKILL.md +3 -4
- package/skills/j-lgtm/SKILL.md +3 -4
- package/skills/j-pi-plan/SKILL.md +3 -4
- package/skills/j-proceed/SKILL.md +3 -4
- package/skills/j-publish/SKILL.md +3 -4
- package/skills/j-publish/scripts/run_gates.sh +1 -1
- package/skills/j-reconcile/SKILL.md +38 -5
- package/skills/j-reconcile/assets/report_format.md +11 -0
- package/skills/j-reconcile/scripts/detect-unlinked-code.sh +2 -2
- package/skills/j-reconcile-origin/SKILL.md +3 -4
- package/skills/j-redo/SKILL.md +3 -4
- package/skills/j-skillify/SKILL.md +3 -4
- package/skills/j-spinoff/SKILL.md +3 -4
- package/skills/j-status/SKILL.md +4 -5
- package/skills/j-todo/SKILL.md +42 -5
- package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
- package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
- package/skills/j-uncharted/SKILL.md +251 -12
- package/skills/j-uncharted/scripts/detect-dependencies.sh +79 -21
- package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
- package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
- package/skills/j-uncharted/scripts/run-engine.sh +36 -2
- package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
- package/skills/j-wtf/SKILL.md +3 -4
- package/skills/jenga/SKILL.md +69 -8
- package/skills/jenga/playbooks/schema.json +4 -4
- package/skills/jenga/scripts/load-nl-catalog.js +5 -2
- package/skills/jenga/scripts/load-playbooks.sh +289 -4
- package/skills/jenga/scripts/match-playbook.sh +4 -4
- package/skills/jenga/scripts/run-playbook-step.sh +267 -1
- package/templates/permission-levels/level-1-locked.json +1 -1
- package/templates/permission-levels/level-2-guarded.json +1 -1
- package/templates/permission-levels/level-3-standard.json +1 -1
- package/templates/permission-levels/level-4-elevated.json +1 -1
- package/templates/permission-levels/level-5-unrestricted.json +1 -1
- package/templates/playbook-types.json +6 -0
- package/project/app/ui/dist/assets/index-BVR_7Owg.css +0 -1
- package/project/app/ui/dist/assets/index-CtU2xLQm.js +0 -104
- package/scripts/audit-twin-divergence.sh +0 -693
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: j.uncharted
|
|
3
|
-
description:
|
|
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
4
|
metadata:
|
|
5
5
|
prefered_agent: scrum-master
|
|
6
6
|
output_types: text
|
|
@@ -14,7 +14,6 @@ keywords:
|
|
|
14
14
|
- "unlinked code"
|
|
15
15
|
- "no board history"
|
|
16
16
|
- j-uncharted
|
|
17
|
-
- polyfill
|
|
18
17
|
examples:
|
|
19
18
|
- "what does this file actually do?"
|
|
20
19
|
- "help me understand this directory I inherited"
|
|
@@ -28,9 +27,9 @@ examples:
|
|
|
28
27
|
|
|
29
28
|
# Uncharted — Investigative Workflow for Foreign & Pre-Existing Code
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
`skills/j-uncharted/` is the **canonical, hand-edited** directory for this skill, per CLAUDE.md's "The Canonical Naming Contract" (the `E50` reopening of 2026-09-09, which promoted `skills/j-uncharted/` from generated twin to sole canonical form). The `j-` prefix is there for collision safety — a real directory under a distinct name, so a host tool shipping its own same-named built-in command cannot shadow it (Claude Code's native skill resolution is a literal-string, directory-name-based match; see `docs/skill-authoring.md`'s "Invocation Convention").
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
> ⚠️ **`scripts/generate-j-alias.sh` was retired by `E50_S14` and no longer exists — there is nothing to run.** This file was previously generated from a bare `skills/uncharted/SKILL.md` source; `E50_S15` deleted that directory. This file is now the sole canonical, hand-edited source for this skill — edit it directly.
|
|
34
33
|
|
|
35
34
|
`/uncharted` is the entry point for code with **no board provenance** — code that was never planned, decomposed, or executed through the Jenga workflow.
|
|
36
35
|
|
|
@@ -58,13 +57,14 @@ Every mode runs the same shared investigative engine and emits the same understa
|
|
|
58
57
|
| `segment` | one file, directory, or feature | Two explicit modes since E20_S08_T03: `--mode delivery` (default, unchanged) analyses a target already in the repo and proposes a standard epic/story/task; `--mode investigate` opens a conversational architecture-investigation flow instead. See **`segment`** below for the mode choice. | E40_S02 / E20_S08_T03 |
|
|
59
58
|
| `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 |
|
|
60
59
|
| `onboard` | the whole codebase | Conversational by default since E20_S08_T03: discovery scripts seed a human-in-the-loop elicitation that writes `[ARCH]`-tagged board items and coarse graph nodes. `--legacy` reproduces the original fully-automated, zero-prompt, capped-**backfilled**-epic pass unchanged. Board/graph-only — never touches application code, in either mode. | E40_S04 / E20_S08_T03 |
|
|
60
|
+
| `refresh` | the delta since a prior scan | Incremental re-scan for an already-`onboard`ed codebase. Locates a baseline (a committed scan-record, or one inferred from board evidence, or — if neither exists — falls back to a full `onboard` run), diffs the codebase against it, and routes only `changed`/`new` candidates through `onboard`'s own mechanics — same bare-vs-`--legacy` split, same Convergence Loop, same backfilled-epic writer. `removed` candidates are never silently dropped: their graph node(s) are marked superseded and the corresponding board item is flagged for review. Board/graph-only — never touches application code, in either mode. See **`refresh`** below. | E40_S07 |
|
|
61
61
|
|
|
62
62
|
**Dispatch rules:**
|
|
63
63
|
|
|
64
|
-
1. If the mode is one of `segment`, `import`, or `
|
|
65
|
-
2. If the mode is missing or unrecognised, do **not** guess. Present the
|
|
64
|
+
1. If the mode is one of `segment`, `import`, `onboard`, or `refresh`, dispatch to that section below.
|
|
65
|
+
2. If the mode is missing or unrecognised, do **not** guess. Present the four modes as a numbered choice list with a free-text option last, per the Interaction Pattern in `CLAUDE.md`.
|
|
66
66
|
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.
|
|
67
|
-
4. Never widen scope across modes in one invocation. `import` may hand off to `segment`
|
|
67
|
+
4. Never widen scope across modes in one invocation. Two, and only two, handoffs are part of a mode's own contract rather than a violation of this rule: `import` may hand off to `segment` (E40_S03_T03), and `refresh` may fall back to running `onboard` in full when no baseline exists for the requested root (see **`refresh`** below, and `onboard`'s own Invocation Contract row). Nothing else chains implicitly.
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -358,7 +358,9 @@ It returns one `results[]` record. Read three fields:
|
|
|
358
358
|
|
|
359
359
|
**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.
|
|
360
360
|
|
|
361
|
-
> **Step 1 is authoritative on linkage.** `run-engine.sh` keeps its own private
|
|
361
|
+
> **Step 1 is authoritative on linkage.** `run-engine.sh` keeps its own private, minimal reimplementation of `resolve-segment-target.sh`'s in-repo linkage check for the document's `Board Linkage` row — as of `E40_S02_T05`, both use the same path-boundary rule (a board reference counts only when a board file's path-like token equals the target or is a path descendant of it, and a board reference to the target's generated `.agents/`/`.claude/` mirror counts as a reference to the canonical root too), so the two should agree for every in-repo target. They can still theoretically diverge — `run-engine.sh`'s version is a minimal reimplementation, not `resolve-segment-target.sh`'s full `BOARD_INDEX`/tokenizer, and full unification (having `run-engine.sh` call `resolve-segment-target.sh --json-only` directly) remains a deliberate, separate follow-up (see below). 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.
|
|
362
|
+
>
|
|
363
|
+
> **When Step 1 reports `not_checked` (e.g. the target is outside this repository), do not render an `unlinked`/`linked` claim at all.** Neither is true — nothing was searched, so nothing was verified absent or present. Render the document's Board Linkage row as `"not applicable — target outside repository"` (or the equivalent `reason` Step 1 returned, worded the same way), and say the same thing out loud per "Report the linkage before doing anything else" above. `run-engine.sh`'s own row for this case is worded consistently (`not_checked — target is outside this repository`), so the two should already agree — but Step 1 stays the source of truth if they ever don't.
|
|
362
364
|
>
|
|
363
365
|
> 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.
|
|
364
366
|
|
|
@@ -660,7 +662,7 @@ exists as a *board-only* mode rather than a generic migration tool. Its entire o
|
|
|
660
662
|
without exception, is:
|
|
661
663
|
|
|
662
664
|
- `project/board/` (backfilled epics in `--legacy` mode; `[ARCH]`-tagged epics/stories/tasks in conversational mode)
|
|
663
|
-
- `project/rapports/analysis/` (understanding documents and the subsystem cap record — `--legacy` mode only; conversational mode's primary output is the graph, not this document type — see Conversational Elicitation above)
|
|
665
|
+
- `project/rapports/analysis/` (understanding documents and the subsystem cap record — `--legacy` mode only; conversational mode's primary output is the graph, not this document type — see Conversational Elicitation above). Both modes additionally write a committed scan-record (`*.scan-record.json`, `E40_S07_T01`) into this same directory at the end of every successful run — distinguished from an understanding document by filename suffix, never overloading one artifact with two purposes.
|
|
664
666
|
- `project/PROJECT_SUMMARY.md` (populated by `E40_S04_T05`, `--legacy` mode only)
|
|
665
667
|
- `project/knowledge-graph/graph.json` (coarse graph nodes/edges — conversational mode only, per the stub schema)
|
|
666
668
|
- `project/queue/elicitation-state/` (multi-session persistence scratch state — conversational mode only, git-ignored, not a durable artifact)
|
|
@@ -683,10 +685,10 @@ stated here:
|
|
|
683
685
|
|
|
684
686
|
New in **E20_S08_T03**. Runs when `onboard` is invoked without `--legacy`.
|
|
685
687
|
|
|
686
|
-
**Step 1 — discovery stays scripted.** Run the exact same deterministic discovery chain the legacy pipeline uses — `discover-subsystems.sh` — unchanged. Evidence-gathering is not where this rework touches anything; only what happens with the output differs.
|
|
688
|
+
**Step 1 — discovery stays scripted.** Run the exact same deterministic discovery chain the legacy pipeline uses — `discover-subsystems.sh` — unchanged. Evidence-gathering is not where this rework touches anything; only what happens with the output differs. Capture its output — Step 5 reuses it to write the scan-record once the run completes successfully.
|
|
687
689
|
|
|
688
690
|
```bash
|
|
689
|
-
bash skills/j-uncharted/scripts/discover-subsystems.sh <root>
|
|
691
|
+
DISCOVERY_JSON=$(bash skills/j-uncharted/scripts/discover-subsystems.sh <root>)
|
|
690
692
|
```
|
|
691
693
|
|
|
692
694
|
**Step 2 — directory triage.** Feed the discovery output's candidate paths into the shared Directory Triage procedure (see Conversational Elicitation above), and stop at its confirmation gate before anything else happens.
|
|
@@ -701,6 +703,14 @@ bash skills/j-uncharted/scripts/elicitation-state.sh init --id "onboard-$(basena
|
|
|
701
703
|
|
|
702
704
|
**Step 5 — on convergence, write the graph and the board item(s).** Per candidate: write the converged node(s)/edge(s) to `project/knowledge-graph/graph.json`, then present an `[ARCH]`-tagged board item proposal at whichever level fits (an individual subsystem is usually story-scale; the whole run may warrant a single `[ARCH]` epic containing one story per converged subsystem — judgement call, not a fixed rule) and stop at a confirmation gate before writing to `project/board/`, exactly as `segment --mode investigate`'s Step 4 does. Call `elicitation-state.sh complete` once every candidate has converged, been deferred, or been resolved past the turn cap.
|
|
703
705
|
|
|
706
|
+
**Write the scan-record once the run has completed successfully** (`elicitation-state.sh complete` has been called) — not on every candidate, and not gated on `refresh` mode existing or ever being invoked (`E40_S07_T01`). Reuse `$DISCOVERY_JSON` captured at Step 1; this is the only place in the conversational path that needs it after Step 2:
|
|
707
|
+
|
|
708
|
+
```bash
|
|
709
|
+
bash skills/j-uncharted/scripts/write-scan-record.sh <<< "$DISCOVERY_JSON"
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
This is deliberately not the elicitation state file — that persistence is transient session scratch (see Multi-Session Persistence above); the scan-record is committed and must survive indefinitely between onboard/refresh runs. Baseline discovery and diff classification against this record are separate, not-yet-implemented follow-on tasks (`E40_S07_T02`, `E40_S07_T03`); this step only writes it.
|
|
713
|
+
|
|
704
714
|
**`PROJECT_SUMMARY.md` population still applies, unchanged in spirit.** Once the conversational pass has produced its `[ARCH]` items, hand off to the scrum-master for the same `PROJECT_SUMMARY.md` Overview/Architecture & Structure drafting-and-confirmation flow described under **Updating PROJECT_SUMMARY.md from onboard evidence** below (Steps A-D) — substituting the conversational pass's converged understanding for the legacy pipeline's `kept` array as the evidence source. Do not skip the stub-vs-real-content check in Step B just because the evidence came from a conversation instead of a script.
|
|
705
715
|
|
|
706
716
|
#### Legacy mode (`--legacy`)
|
|
@@ -765,7 +775,8 @@ that actually writes backfilled epics to the board — it consumes `apply-subsys
|
|
|
765
775
|
framed around understanding and integration, never original construction):
|
|
766
776
|
|
|
767
777
|
```bash
|
|
768
|
-
bash skills/j-uncharted/scripts/discover-subsystems.sh <root>
|
|
778
|
+
DISCOVERY_JSON=$(bash skills/j-uncharted/scripts/discover-subsystems.sh <root>)
|
|
779
|
+
echo "$DISCOVERY_JSON" \
|
|
769
780
|
| bash skills/j-uncharted/scripts/apply-subsystem-cap.sh --rapport "$DOC" \
|
|
770
781
|
| bash skills/j-uncharted/scripts/write-backfilled-epics.sh
|
|
771
782
|
```
|
|
@@ -777,6 +788,22 @@ bash skills/j-uncharted/scripts/discover-subsystems.sh <root> \
|
|
|
777
788
|
| `--dry-run` | Compute IDs and render content without writing anything — useful for previewing what a run would produce. |
|
|
778
789
|
| `--label "<text>"` | Human label for the analysed codebase, used in each epic's Purpose section. |
|
|
779
790
|
|
|
791
|
+
**Write the scan-record once epic generation has succeeded** (`E40_S07_T01`) — not gated on
|
|
792
|
+
`refresh` mode existing or ever being invoked; every successful `onboard --legacy` run leaves one,
|
|
793
|
+
the same as the conversational default does. Reuse `$DISCOVERY_JSON` captured above — it carries
|
|
794
|
+
`discover-subsystems.sh`'s own `candidates` array, which is what `write-scan-record.sh` expects;
|
|
795
|
+
`apply-subsystem-cap.sh`'s `kept`/`dropped` output is a different shape and is not what gets piped
|
|
796
|
+
here:
|
|
797
|
+
|
|
798
|
+
```bash
|
|
799
|
+
bash skills/j-uncharted/scripts/write-scan-record.sh <<< "$DISCOVERY_JSON"
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
This is a committed record (never git-ignored), distinct from `elicitation-state.sh`'s transient
|
|
803
|
+
session scratch — see the conversational default's own note on this above. Baseline discovery and
|
|
804
|
+
diff classification against it are separate, not-yet-implemented follow-on tasks (`E40_S07_T02`,
|
|
805
|
+
`E40_S07_T03`); this step only writes it.
|
|
806
|
+
|
|
780
807
|
**Epic ID continuation.** The next free `E##` is one past the highest epic number found under
|
|
781
808
|
both `--epics-dir` and the canonical `project/board/epics/`, so generated epics always continue
|
|
782
809
|
the existing board's numbering and never collide with an epic already on it.
|
|
@@ -910,6 +937,218 @@ Like epic generation above it, "skip" on both sections is a valid outcome, not a
|
|
|
910
937
|
that leaves `PROJECT_SUMMARY.md` untouched still respects the read/write surface named under **The
|
|
911
938
|
hard constraint** above, since that file was never a required write, only a permitted one.
|
|
912
939
|
|
|
940
|
+
### `refresh`
|
|
941
|
+
|
|
942
|
+
Incremental re-scan for a codebase that has already been through `onboard` at least once. Detects
|
|
943
|
+
what changed since the last scan and only reinvestigates the delta, so keeping the board and graph
|
|
944
|
+
current does not mean re-running the whole of `onboard` every time. Like `onboard`, `refresh` has
|
|
945
|
+
the same bare-vs-`--legacy` split (see the Invocation Contract table above) — read **Conversational
|
|
946
|
+
Elicitation** above (Human-Oracle-Availability Limitation, Directory Triage, Convergence Loop,
|
|
947
|
+
Multi-Session Persistence) before running the conversational default. Everything below only
|
|
948
|
+
sequences those shared mechanics, plus the three baseline/diff scripts introduced by this story
|
|
949
|
+
(`E40_S07`), for `refresh`'s own scope — it does not redefine any of them. `refresh` is also bound,
|
|
950
|
+
without exception or restatement, by the **Constraints** section below — read-only against
|
|
951
|
+
application code, confirm before writing to the board, no new rapport type — the same as every
|
|
952
|
+
other mode.
|
|
953
|
+
|
|
954
|
+
**`refresh` introduces no new understanding mechanism.** Every node, epic, story, and task it
|
|
955
|
+
writes goes through exactly the same write paths `onboard` already uses — the Convergence Loop for
|
|
956
|
+
the conversational default, `write-backfilled-epics.sh` for `--legacy`. `refresh` only changes
|
|
957
|
+
*which* candidates reach those paths: `find-scan-baseline.sh` (`E40_S07_T02`) locates a baseline
|
|
958
|
+
and `diff-since-baseline.sh` (`E40_S07_T03`) classifies each previously-known candidate as
|
|
959
|
+
`unchanged`/`changed`/`new`/`removed` before either write path ever runs.
|
|
960
|
+
|
|
961
|
+
#### Baseline discovery and diff — shared by both modes
|
|
962
|
+
|
|
963
|
+
Read `find-scan-baseline.sh`'s and `diff-since-baseline.sh`'s own header comments before invoking
|
|
964
|
+
either — their exact flag names and exit-code numbers are load-bearing for this sequencing, and
|
|
965
|
+
are not restated in full here (per **Shared Investigative Engine**'s "do not restate in prose what
|
|
966
|
+
these scripts do step by step" convention above).
|
|
967
|
+
|
|
968
|
+
**Step 1 — run fresh discovery once, keep it.** Both the diff below and (for `--legacy`) the
|
|
969
|
+
backfill step later need the same fresh `discover-subsystems.sh` output, so it is captured once
|
|
970
|
+
here rather than re-run per step — the same pattern `onboard`'s own Step 1 already uses:
|
|
971
|
+
|
|
972
|
+
```bash
|
|
973
|
+
DISCOVERY_JSON=$(bash skills/j-uncharted/scripts/discover-subsystems.sh <root>)
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
**Step 2 — locate a baseline.**
|
|
977
|
+
|
|
978
|
+
```bash
|
|
979
|
+
BASELINE_JSON=$(bash skills/j-uncharted/scripts/find-scan-baseline.sh <root>); RC=$?
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
Three outcomes, per the script's own exit-code contract:
|
|
983
|
+
|
|
984
|
+
- **Exit 0, `"source": "scan-record"`.** The precise case — a prior `write-scan-record.sh` run for
|
|
985
|
+
this root was found under `project/rapports/analysis/`. Its `candidates[]` array (`path` +
|
|
986
|
+
`files`/`lines` per candidate) is what Step 3 below diffs against.
|
|
987
|
+
- **Exit 0, `"source": "inferred"`.** No scan-record exists, but a `provenance: backfilled` epic or
|
|
988
|
+
an `[ARCH]`-tagged board item whose `docs:` overlaps the root gave an approximate baseline commit
|
|
989
|
+
via git history on that board file. **This shape carries no `candidates[]` array at all** — only
|
|
990
|
+
`baseline_commit` and `inferred_from[]`. Say this plainly to the user before proceeding: an
|
|
991
|
+
inferred baseline has no prior candidate set to compare *membership* against, so `diff-since-
|
|
992
|
+
baseline.sh` (Step 3) can only ever classify candidates as `unchanged`/`changed` against it — its
|
|
993
|
+
`new`/`removed` arrays stay empty by construction. A `refresh` against an inferred baseline
|
|
994
|
+
cannot tell you a subsystem disappeared or a brand-new one appeared, only that a previously-known
|
|
995
|
+
one did or didn't change. Do not imply otherwise to the user.
|
|
996
|
+
- **Exit 3 — no baseline found.** Neither a scan-record nor board evidence exists for this root.
|
|
997
|
+
**Stop here and run `onboard`'s own conversational default in full** (its Steps 1-5 above,
|
|
998
|
+
including its own scan-record write at the end) — this *is* the first scan for this root, and
|
|
999
|
+
nothing else in this `refresh` subsection applies to that run. The *next* `refresh` invocation
|
|
1000
|
+
against this root then finds that scan-record at Tier 1. `--legacy refresh` takes the equivalent
|
|
1001
|
+
fallback: a full `onboard --legacy` run, which likewise ends by writing the first scan-record per
|
|
1002
|
+
its own "Write the scan-record once epic generation has succeeded" step above.
|
|
1003
|
+
|
|
1004
|
+
Any other exit (1 usage error, 2 environment error, 4 write failure) is not "no baseline" — surface
|
|
1005
|
+
the script's stderr and fix the invocation rather than falling back to `onboard`.
|
|
1006
|
+
|
|
1007
|
+
**Step 3 — classify what changed.**
|
|
1008
|
+
|
|
1009
|
+
```bash
|
|
1010
|
+
DIFF_JSON=$(echo "$BASELINE_JSON" \
|
|
1011
|
+
| bash skills/j-uncharted/scripts/diff-since-baseline.sh --fresh <(echo "$DISCOVERY_JSON"))
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
`--fresh` is passed explicitly here (rather than letting the script re-run discovery itself)
|
|
1015
|
+
specifically so Step 1's `$DISCOVERY_JSON` is the one candidate set both this diff and the
|
|
1016
|
+
`--legacy` backfill step below reason about — one discovery pass per `refresh` run, not two that
|
|
1017
|
+
could in principle disagree if the tree changed between them. Read back `unchanged`, `changed`,
|
|
1018
|
+
`new`, and `removed` (each entry carrying at least `path`; `changed` entries also carry
|
|
1019
|
+
`changed_files`) — see the script's own header for the full shape. As Step 2 already flagged, a
|
|
1020
|
+
Tier 2 (`inferred`) baseline always yields empty `new`/`removed` arrays; that is documented,
|
|
1021
|
+
intentional behavior of the script, not a defect in this sequencing.
|
|
1022
|
+
|
|
1023
|
+
#### Conversational default
|
|
1024
|
+
|
|
1025
|
+
Runs when `refresh` is invoked without `--legacy`, once Steps 1-3 above have produced `$DIFF_JSON`.
|
|
1026
|
+
|
|
1027
|
+
1. **`unchanged` candidates are skipped entirely.** No Familiarity Check, no Directory Triage
|
|
1028
|
+
entry, no prompt of any kind — they never enter the candidate set below at all.
|
|
1029
|
+
2. **`changed` and `new` candidates proceed through Directory Triage (if applicable) and the
|
|
1030
|
+
Convergence Loop exactly as `onboard`'s conversational default already does** (its Steps 2-5
|
|
1031
|
+
above) — no new understanding mechanism, no different gating, no different question templates.
|
|
1032
|
+
The candidate set fed to Directory Triage is the union of `$DIFF_JSON`'s `changed` and `new`
|
|
1033
|
+
paths, in place of `onboard`'s own full `discover-subsystems.sh` output:
|
|
1034
|
+
|
|
1035
|
+
```bash
|
|
1036
|
+
echo "$DIFF_JSON" | jq -r '.changed[].path, .new[].path' \
|
|
1037
|
+
| bash skills/j-uncharted/scripts/directory-triage.sh <root>
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
Initialize persistence the same way `onboard`'s own Step 3 does, with an id that distinguishes a
|
|
1041
|
+
`refresh` run from an `onboard` run over the same root:
|
|
1042
|
+
|
|
1043
|
+
```bash
|
|
1044
|
+
bash skills/j-uncharted/scripts/elicitation-state.sh init \
|
|
1045
|
+
--id "refresh-$(basename "$(cd "$root" && pwd)")-$(date -u +%Y%m%d)" --target "<root>" --cap 5
|
|
1046
|
+
```
|
|
1047
|
+
3. **`removed` candidates are never silently deleted or status-changed.** For each entry in
|
|
1048
|
+
`$DIFF_JSON`'s `removed` array:
|
|
1049
|
+
- **Locate the corresponding graph node(s)** in `project/knowledge-graph/graph.json` — the
|
|
1050
|
+
node(s) written for that path during the run that originally converged it. The stub schema
|
|
1051
|
+
(`project/knowledge-graph/STUB_SCHEMA.md`) has no dedicated path field on a node, so this
|
|
1052
|
+
correspondence is read from what the node's own `label`/`description` and edges recorded
|
|
1053
|
+
about the candidate at convergence time, not looked up by a formal key. Where more than one
|
|
1054
|
+
node plausibly corresponds and the traces don't disambiguate, say so under the board-item
|
|
1055
|
+
flag below rather than guessing which one to mark.
|
|
1056
|
+
- **Mark it `status: superseded`**, per the stub schema's Evidence-Wins Conflict Rule. That
|
|
1057
|
+
rule's `superseded_by` field is documented as "required when `status: superseded`... points at
|
|
1058
|
+
the node that superseded this one," and every worked example in the stub schema is a real
|
|
1059
|
+
replacement node — there is no successor node for a subsystem that was simply removed. Rather
|
|
1060
|
+
than inventing a new schema field to express "no successor" (the stub schema is deliberately
|
|
1061
|
+
minimal per its own header, and the Human-Oracle-Availability Limitation above already
|
|
1062
|
+
establishes the convention for an unrepresentable nuance: say it in the node's `description`
|
|
1063
|
+
text, not a new field), set `superseded_by` to a plain sentinel string that cannot be mistaken
|
|
1064
|
+
for a real node id — `"removed-no-successor"` — and state the reason in the node's own
|
|
1065
|
+
`description`, e.g. "Subsystem removed from the codebase as of the `refresh` run on
|
|
1066
|
+
<YYYY-MM-DD>; no successor node exists." **This sentinel is `refresh`'s own documented
|
|
1067
|
+
convention, not a stub-schema addition** — if it's ever questioned, say so plainly rather than
|
|
1068
|
+
treating it as if the stub schema itself defined it.
|
|
1069
|
+
- **Flag the corresponding board item for review**, reusing existing mechanisms rather than a
|
|
1070
|
+
new frontmatter field (`templates/SCRUM_BOARD_SCHEMA.md` has no `flagged_for_review` field or
|
|
1071
|
+
equivalent, and adding one is a schema change for the scrum-master to make, not something this
|
|
1072
|
+
skill invents in passing):
|
|
1073
|
+
- If the item is already in a terminal status (`Passed`, `Passed with remarks`, `Rejected`,
|
|
1074
|
+
`Done`, `Blocked`), set `reopened_on`/`reopened_reason` per the schema's Reopen Tracking
|
|
1075
|
+
Fields, with a reason naming the removed path and the superseded node's id — **without**
|
|
1076
|
+
changing `status` itself. This keeps the flag visible on the item without the side effect of
|
|
1077
|
+
silently reopening it into active work; a human decides whether it actually needs reopening.
|
|
1078
|
+
- Regardless of status, also append a dated prose note to the board item's body — the same
|
|
1079
|
+
"Reconcile Note" convention already used elsewhere on this board (see e.g.
|
|
1080
|
+
`project/board/stories/E40_S01_shared-investigative-engine.md`'s "Reconcile Note —
|
|
1081
|
+
2026-09-23" section for a live example) — naming the removed path, the superseded node id,
|
|
1082
|
+
and the `refresh` run's date, so a reader sees why the item needs a look even before
|
|
1083
|
+
checking `reopened_reason`.
|
|
1084
|
+
- **Never delete the board item and never change its `status` field** as part of this
|
|
1085
|
+
flagging step — status changes remain the tester's exclusive responsibility (per
|
|
1086
|
+
`agents/developer.md` and `agents/tester.md`), and this is a flag for a human to act on, not
|
|
1087
|
+
an automatic resolution.
|
|
1088
|
+
|
|
1089
|
+
Both the conversational path and `--legacy` (below) use this exact supersede-and-flag treatment
|
|
1090
|
+
for `removed` candidates — it is not conversational-only.
|
|
1091
|
+
|
|
1092
|
+
**Write the scan-record once this run completes** (`elicitation-state.sh complete` has been
|
|
1093
|
+
called), exactly as `onboard`'s conversational default's own step does. Reuse `$DISCOVERY_JSON`
|
|
1094
|
+
from Step 1 above — the full fresh discovery output, not `$DIFF_JSON`'s classified subset, and not
|
|
1095
|
+
only the `changed`/`new` candidates that went through the Convergence Loop — so the next baseline
|
|
1096
|
+
correctly reflects every candidate currently present, including the `unchanged` ones that were
|
|
1097
|
+
skipped this run:
|
|
1098
|
+
|
|
1099
|
+
```bash
|
|
1100
|
+
bash skills/j-uncharted/scripts/write-scan-record.sh <<< "$DISCOVERY_JSON"
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
**`PROJECT_SUMMARY.md` population applies the same way `onboard`'s conversational default already
|
|
1104
|
+
hands it off** — see **Updating PROJECT_SUMMARY.md from onboard evidence** above (Steps A-D),
|
|
1105
|
+
substituting this run's converged understanding (the `changed`/`new` candidates only) as the
|
|
1106
|
+
evidence source, the same substitution `onboard`'s own conversational default already makes for its
|
|
1107
|
+
own evidence.
|
|
1108
|
+
|
|
1109
|
+
#### `--legacy` mode
|
|
1110
|
+
|
|
1111
|
+
Baseline discovery and diff are exactly the shared Steps 1-3 above — there is no `--legacy`-specific
|
|
1112
|
+
variant of `find-scan-baseline.sh` or `diff-since-baseline.sh`.
|
|
1113
|
+
|
|
1114
|
+
1. **Backfill only `changed`/`new` subsystems.** `apply-subsystem-cap.sh` requires `candidates[]`
|
|
1115
|
+
entries carrying `rank` and `score` (see its own header) — fields `diff-since-baseline.sh`'s
|
|
1116
|
+
`changed`/`new` entries do not carry (they carry `path`/`files`/`lines`, plus `changed_files` for
|
|
1117
|
+
`changed`). Building a synthetic report from `$DIFF_JSON` directly would therefore be missing
|
|
1118
|
+
fields `apply-subsystem-cap.sh` actually needs. Instead, filter Step 1's full
|
|
1119
|
+
`$DISCOVERY_JSON` — which already carries `rank`/`score` for every candidate — down to just the
|
|
1120
|
+
paths `$DIFF_JSON` classified as `changed` or `new`:
|
|
1121
|
+
|
|
1122
|
+
```bash
|
|
1123
|
+
CHANGED_NEW_PATHS=$(echo "$DIFF_JSON" | jq -c '[.changed[].path, .new[].path]')
|
|
1124
|
+
FILTERED_REPORT=$(echo "$DISCOVERY_JSON" | jq --argjson keep "$CHANGED_NEW_PATHS" \
|
|
1125
|
+
'.candidates |= map(select(.path as $p | $keep | index($p) != null))')
|
|
1126
|
+
|
|
1127
|
+
echo "$FILTERED_REPORT" \
|
|
1128
|
+
| bash skills/j-uncharted/scripts/apply-subsystem-cap.sh --rapport "$DOC" \
|
|
1129
|
+
| bash skills/j-uncharted/scripts/write-backfilled-epics.sh
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
This is the "backfill only" behavior the story's Design section agrees to (`E40_S07` Design,
|
|
1133
|
+
point 6): existing epics for `unchanged` subsystems are never touched, never re-capped, never
|
|
1134
|
+
renumbered — they simply never enter `apply-subsystem-cap.sh`'s input. The subsystem cap
|
|
1135
|
+
(default 8, `apply-subsystem-cap.sh --cap N`) still applies, but now scoped to the changed/new
|
|
1136
|
+
set only, not the full fresh discovery output the way a plain `onboard --legacy` run scopes it.
|
|
1137
|
+
2. **`removed` candidates get the identical graph-node-supersede-and-flag treatment** described
|
|
1138
|
+
under the conversational default's step 3 above — mark the corresponding graph node(s)
|
|
1139
|
+
`status: superseded` with the `removed-no-successor` sentinel, and flag the board item via
|
|
1140
|
+
`reopened_on`/`reopened_reason` (terminal items) plus a dated Reconcile Note (any item), never a
|
|
1141
|
+
silent delete or status change. This part of the behavior is identical between the two modes.
|
|
1142
|
+
3. **Write the scan-record once epic generation succeeds**, exactly as a plain `onboard --legacy`
|
|
1143
|
+
run's own step does. Reuse the full `$DISCOVERY_JSON` from Step 1 above — not the filtered
|
|
1144
|
+
changed/new-only report built for `apply-subsystem-cap.sh` — for the same reason the
|
|
1145
|
+
conversational default reuses the full set: the next baseline must reflect everything currently
|
|
1146
|
+
present, including the `unchanged` subsystems this run never touched:
|
|
1147
|
+
|
|
1148
|
+
```bash
|
|
1149
|
+
bash skills/j-uncharted/scripts/write-scan-record.sh <<< "$DISCOVERY_JSON"
|
|
1150
|
+
```
|
|
1151
|
+
|
|
913
1152
|
---
|
|
914
1153
|
|
|
915
1154
|
## Constraints
|
|
@@ -183,6 +183,7 @@ EXT_LANG = {
|
|
|
183
183
|
".cc": "cpp", ".cpp": "cpp", ".cxx": "cpp", ".hpp": "cpp", ".hh": "cpp",
|
|
184
184
|
".java": "java", ".kt": "kotlin", ".kts": "kotlin",
|
|
185
185
|
".php": "php",
|
|
186
|
+
".cs": "csharp",
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
SHEBANG_LANG = [
|
|
@@ -539,6 +540,33 @@ def scan_php(text, path, base_dir, language, source):
|
|
|
539
540
|
add_external(spec.lstrip("\\").split("\\")[0], language, "package", source)
|
|
540
541
|
|
|
541
542
|
|
|
543
|
+
CSHARP_USING = re.compile(r"^\s*using\s+(?:static\s+)?([\w.]+)\s*;", re.M)
|
|
544
|
+
# Dot-bounded, matching scan_jvm's JVM_STDLIB_PREFIXES convention: a bare prefix match
|
|
545
|
+
# (e.g. "System") would also swallow unrelated namespaces like "SystemUnderTest.Helpers"
|
|
546
|
+
# or "Systemantics.Foo". "System" and "Microsoft" are also valid bare (undotted) using
|
|
547
|
+
# targets on their own (e.g. "using System;"), so they're checked as an exact match too.
|
|
548
|
+
CSHARP_STDLIB_PREFIXES = ("System.", "Microsoft.")
|
|
549
|
+
CSHARP_STDLIB_EXACT = ("System", "Microsoft")
|
|
550
|
+
CSHARP_SRC_ROOTS = ("", "src", "Source")
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def scan_csharp(text, path, base_dir, language, source):
|
|
554
|
+
for spec in CSHARP_USING.findall(text):
|
|
555
|
+
if spec in CSHARP_STDLIB_EXACT or spec.startswith(CSHARP_STDLIB_PREFIXES):
|
|
556
|
+
add_external(spec, language, "stdlib", source)
|
|
557
|
+
continue
|
|
558
|
+
as_path = spec.replace(".", "/")
|
|
559
|
+
resolved = None
|
|
560
|
+
for src_root in CSHARP_SRC_ROOTS:
|
|
561
|
+
resolved = resolve_path(os.path.join(REPO_ROOT, src_root), as_path, [".cs"])
|
|
562
|
+
if resolved:
|
|
563
|
+
break
|
|
564
|
+
if resolved:
|
|
565
|
+
add_internal(spec, language, resolved, source)
|
|
566
|
+
else:
|
|
567
|
+
add_external(spec, language, "package", source)
|
|
568
|
+
|
|
569
|
+
|
|
542
570
|
SCANNERS = {
|
|
543
571
|
"javascript": scan_js, "typescript": scan_js,
|
|
544
572
|
"python": scan_python,
|
|
@@ -549,6 +577,7 @@ SCANNERS = {
|
|
|
549
577
|
"c": scan_c, "cpp": scan_c,
|
|
550
578
|
"java": scan_jvm, "kotlin": scan_jvm,
|
|
551
579
|
"php": scan_php,
|
|
580
|
+
"csharp": scan_csharp,
|
|
552
581
|
}
|
|
553
582
|
|
|
554
583
|
# ---------------------------------------------------------------------------
|
|
@@ -559,6 +588,7 @@ MANIFEST_NAMES = [
|
|
|
559
588
|
"package.json", "pyproject.toml", "requirements.txt", "go.mod", "Cargo.toml", "Gemfile",
|
|
560
589
|
"setup.py", "setup.cfg", "Pipfile", "composer.json", "pom.xml",
|
|
561
590
|
"build.gradle", "build.gradle.kts", "Package.swift", "mix.exs", "pubspec.yaml",
|
|
591
|
+
"packages.config", ".csproj",
|
|
562
592
|
]
|
|
563
593
|
|
|
564
594
|
|
|
@@ -620,41 +650,69 @@ def _declared_pyproject(text):
|
|
|
620
650
|
return sorted({n for n in names if n})
|
|
621
651
|
|
|
622
652
|
|
|
653
|
+
CSPROJ_PACKAGE_REF = re.compile(r"""<PackageReference\s+[^>]*\bInclude\s*=\s*["']([^"']+)["']""")
|
|
654
|
+
PACKAGES_CONFIG_PACKAGE = re.compile(r"""<package\s+[^>]*\bid\s*=\s*["']([^"']+)["']""")
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def _declared_csproj(text):
|
|
658
|
+
return sorted(set(CSPROJ_PACKAGE_REF.findall(text)))
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _declared_packages_config(text):
|
|
662
|
+
return sorted(set(PACKAGES_CONFIG_PACKAGE.findall(text)))
|
|
663
|
+
|
|
664
|
+
|
|
623
665
|
DECLARED_PARSERS = {
|
|
624
666
|
"package.json": _declared_package_json,
|
|
625
667
|
"requirements.txt": _declared_requirements,
|
|
626
668
|
"go.mod": _declared_go_mod,
|
|
627
669
|
"Cargo.toml": _declared_cargo,
|
|
628
670
|
"pyproject.toml": _declared_pyproject,
|
|
671
|
+
".csproj": _declared_csproj,
|
|
672
|
+
"packages.config": _declared_packages_config,
|
|
629
673
|
}
|
|
630
674
|
|
|
631
675
|
|
|
632
676
|
def collect_manifests(start_dir):
|
|
633
677
|
found = []
|
|
634
678
|
for directory, distance in find_upwards(start_dir, None):
|
|
679
|
+
dir_entries = None
|
|
635
680
|
for name in MANIFEST_NAMES:
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if parser:
|
|
642
|
-
text = read_text(candidate)
|
|
643
|
-
if text is not None:
|
|
681
|
+
# Entries starting with "." (e.g. ".csproj") are extension globs rather than
|
|
682
|
+
# fixed filenames, since a .csproj's basename varies per project. Everything
|
|
683
|
+
# else keeps the original exact-filename lookup.
|
|
684
|
+
if name.startswith("."):
|
|
685
|
+
if dir_entries is None:
|
|
644
686
|
try:
|
|
645
|
-
|
|
646
|
-
except
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
687
|
+
dir_entries = sorted(os.listdir(directory))
|
|
688
|
+
except OSError:
|
|
689
|
+
dir_entries = []
|
|
690
|
+
matches = [e for e in dir_entries if e.endswith(name)]
|
|
691
|
+
else:
|
|
692
|
+
matches = [name] if os.path.isfile(os.path.join(directory, name)) else []
|
|
693
|
+
for match_name in matches:
|
|
694
|
+
candidate = os.path.join(directory, match_name)
|
|
695
|
+
if not os.path.isfile(candidate):
|
|
696
|
+
continue
|
|
697
|
+
declared = None
|
|
698
|
+
parser = DECLARED_PARSERS.get(name)
|
|
699
|
+
if parser:
|
|
700
|
+
text = read_text(candidate)
|
|
701
|
+
if text is not None:
|
|
702
|
+
try:
|
|
703
|
+
declared = parser(text)
|
|
704
|
+
except Exception:
|
|
705
|
+
notices.append(
|
|
706
|
+
"Could not parse declared dependencies from %s; manifest is listed "
|
|
707
|
+
"but declared_dependencies is null." % rel(candidate)
|
|
708
|
+
)
|
|
709
|
+
found.append({
|
|
710
|
+
"name": match_name,
|
|
711
|
+
"path": rel(candidate),
|
|
712
|
+
"location": "target" if distance == 0 else "ancestor",
|
|
713
|
+
"distance": distance,
|
|
714
|
+
"declared_dependencies": declared,
|
|
715
|
+
})
|
|
658
716
|
return found
|
|
659
717
|
|
|
660
718
|
|