shapeup-sdlc 3.18.2 → 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/.claude/settings.local.example.json +2 -3
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +115 -56
- package/README.md +101 -143
- package/SECURITY.md +16 -25
- package/agents/orient.md +14 -0
- package/agents/qa-edge-hunter.md +13 -0
- package/agents/spec-evaluator.md +14 -0
- package/agents/task-executor.md +14 -0
- package/bin/init.mjs +13 -16
- package/bin/lib/grant.mjs +8 -19
- package/commands/build.md +9 -14
- package/commands/eval.md +3 -3
- package/commands/hammer.md +9 -5
- package/commands/retro.md +12 -11
- package/commands/scopes.md +1 -12
- package/commands/ship.md +45 -88
- package/commands/wire.md +1 -8
- package/hooks/dispatch-receipt.mjs +94 -8
- package/hooks/hooks.json +0 -41
- package/hooks/lib/decision.mjs +6 -9
- package/hooks/lib/tier.mjs +50 -0
- package/hooks/sandbox-guard.mjs +140 -64
- package/kernel/gate.mjs +203 -22
- package/kernel/harness.mjs +20 -67
- package/kernel/init/run.mjs +175 -523
- package/kernel/lib/contract.mjs +9 -93
- package/kernel/lib/floor.mjs +64 -0
- package/kernel/lib/paths.mjs +39 -112
- package/kernel/order.mjs +157 -0
- package/kernel/probe/digest.mjs +2 -38
- package/kernel/schemas/domain.schema.json +146 -1125
- package/kernel/schemas/work-order.schema.json +8 -4
- package/kernel/schemas/work-result.schema.json +1 -1
- package/kernel/verify/envelope.mjs +2 -2
- package/kernel/verify/skills.mjs +27 -8
- package/kernel/verify/t0.mjs +2 -2
- package/package.json +4 -2
- package/skills/building/SKILL.md +144 -0
- package/skills/building/resources/gates.md +76 -0
- package/skills/building/resources/guidance.md +23 -0
- package/skills/{translator/references/preservation-rules.md → building/resources/intake-preservation.md} +1 -1
- package/skills/building/resources/intake.md +157 -0
- package/skills/building/resources/orders.md +69 -0
- package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/doc-schemas.md +1 -1
- package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/task-generation.md +13 -13
- package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/test-surface.md +6 -7
- package/skills/building/resources/plan-scopes.md +95 -0
- package/skills/building/resources/plan-spec.md +102 -0
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/_index.tmpl.md +1 -1
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/assess-report.tmpl.md +10 -11
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/run-state.tmpl.md +4 -4
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/scope-summary.tmpl.md +2 -2
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/synthesis.tmpl.md +4 -4
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/usecase.tmpl.md +2 -2
- package/skills/building/resources/plan-wire.md +82 -0
- package/skills/building/resources/plan.md +98 -0
- package/skills/building/resources/retro-research.md +60 -0
- package/skills/building/resources/retro-scan.md +49 -0
- package/skills/building/resources/retro.md +225 -0
- package/skills/building/resources/rounds.md +78 -0
- package/skills/building/resources/stop.md +127 -0
- package/skills/orient/SKILL.md +37 -37
- package/skills/qa-edge-hunter/SKILL.md +29 -14
- package/skills/shapeup/SKILL.md +5 -5
- package/skills/shapeup/resources/breadboarding.md +1 -1
- package/skills/shapeup/resources/context-compaction.md +5 -5
- package/skills/shapeup/resources/shaping.md +1 -1
- package/skills/spec-evaluator/SKILL.md +17 -21
- package/skills/spec-evaluator/references/dimension-contract.md +1 -1
- package/skills/spec-evaluator/references/dimensions/completeness.md +3 -3
- package/skills/spec-evaluator/references/dimensions/spec-conformance.md +1 -1
- package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +5 -5
- package/skills/task-executor/SKILL.md +11 -15
- package/commands/hill.md +0 -10
- package/hooks/gate-intake.mjs +0 -109
- package/hooks/gate-zerowork.mjs +0 -349
- package/hooks/tier-guard.mjs +0 -162
- package/kernel/compile.mjs +0 -1419
- package/kernel/init/fit.mjs +0 -199
- package/kernel/init/run-args.mjs +0 -206
- package/kernel/lib/breadboard.mjs +0 -165
- package/kernel/probe/attempts.mjs +0 -161
- package/kernel/probe/concurrency.mjs +0 -535
- package/kernel/probe/eval.mjs +0 -405
- package/kernel/probe/hunt.mjs +0 -104
- package/kernel/probe/leg.mjs +0 -229
- package/kernel/probe/owner.mjs +0 -142
- package/kernel/probe/requirements.mjs +0 -328
- package/kernel/probe/resume.mjs +0 -1110
- package/kernel/probe/rounds.mjs +0 -115
- package/kernel/probe/staged.mjs +0 -83
- package/kernel/probe/stats.mjs +0 -477
- package/kernel/probe/t0.mjs +0 -115
- package/kernel/reduce/board.mjs +0 -350
- package/kernel/reduce/graph.mjs +0 -482
- package/kernel/reduce/hill.mjs +0 -322
- package/kernel/reduce/ingest.mjs +0 -767
- package/kernel/reduce/leftovers.mjs +0 -159
- package/kernel/reduce/ship.mjs +0 -529
- package/kernel/reduce/snapshot.mjs +0 -291
- package/kernel/reduce/verdict.mjs +0 -172
- package/kernel/report/export.mjs +0 -384
- package/kernel/report/facts.mjs +0 -234
- package/kernel/verify/budget.mjs +0 -153
- package/kernel/verify/build.mjs +0 -428
- package/kernel/verify/dispatch.mjs +0 -114
- package/kernel/verify/spec.mjs +0 -975
- package/kernel/verify/trace.mjs +0 -604
- package/skills/ba-pitch-analyzer/SKILL.md +0 -204
- package/skills/coach/SKILL.md +0 -397
- package/skills/hill-chart/SKILL.md +0 -154
- package/skills/hill-chart/assets/dashboard.template.html +0 -1432
- package/skills/scope-architect/SKILL.md +0 -174
- package/skills/scope-hammer/SKILL.md +0 -231
- package/skills/solution-architect/SKILL.md +0 -158
- package/skills/tech-lead/SKILL.md +0 -151
- package/skills/tech-lead/references/gates.md +0 -605
- package/skills/tech-lead/references/protocol.md +0 -889
- package/skills/tech-lead/references/tiny-lane.md +0 -53
- package/skills/tech-lead/workflows/shapeup-run.js +0 -2187
- package/skills/translator/SKILL.md +0 -258
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/contract-patterns.md +0 -0
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/ddd-patterns.md +0 -0
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/integration-analysis.md +0 -0
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/ux-behavior-patterns.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/api-feasibility.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/contracts/be-service.contract.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/contracts/offline-storage.contract.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/contracts/third-party-api.contract.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/context-map.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/event-choreography.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/migration-plan.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/team-handoff.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/domain-model.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/feedback.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/integration.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/task-board.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/task-spike.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/task.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/ux-behavior.tmpl.md +0 -0
package/kernel/lib/contract.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// contract — read and write the committed contracts as markdown.
|
|
2
2
|
//
|
|
3
|
-
// WHY
|
|
3
|
+
// WHY. Scope contracts, the wiring map and the project profile are *low-level design*:
|
|
4
4
|
// which files a slice may touch, which seam each use case attaches to, where the app starts. A
|
|
5
5
|
// teammate should be able to read them in a pull request. As `.json` they were machine artifacts
|
|
6
6
|
// sitting in the tier meant for prose, and nobody read them.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
// • scalars and string arrays → frontmatter, in the `key: value` / `key: [a, b]` dialect the
|
|
14
14
|
// repo already parses in `compile-order.frontmatter`;
|
|
15
|
-
// • arrays of objects → markdown TABLES, which `
|
|
15
|
+
// • arrays of objects → markdown TABLES, which `requirements.parseRequirements` and
|
|
16
16
|
// `compile-order.ledgerDecisions` already demonstrate reading;
|
|
17
17
|
// • everything else in the file is prose, and is preserved on round-trip.
|
|
18
18
|
//
|
|
@@ -40,7 +40,7 @@ import { join } from "node:path";
|
|
|
40
40
|
// `signatures` names the columns that identify a table as THAT field's, and it exists because of
|
|
41
41
|
// The heading match is exact, so a table written under `# Wiring map — <slug>` instead of
|
|
42
42
|
// `## Wiring` parsed as ABSENT, and every reader downstream treats absent as "none declared". The
|
|
43
|
-
// observed consequence was
|
|
43
|
+
// observed consequence was a wiring check reporting green with 0/0 engines reached
|
|
44
44
|
// for a committed map holding six correct rows — the gate whose whole purpose is that no engine
|
|
45
45
|
// ships orphaned, failing OPEN.
|
|
46
46
|
//
|
|
@@ -60,7 +60,7 @@ export const SCOPE_CONTRACT = {
|
|
|
60
60
|
* `WiringMap` — per use case: engine → seam → entry-point call site → affordance.
|
|
61
61
|
*
|
|
62
62
|
* `perSection` is a MIGRATION READER, and it exists because the canonical shape was never written.
|
|
63
|
-
*
|
|
63
|
+
* The wiring author was told to produce a `{schema_version, feature, entry_point, entries[]}`
|
|
64
64
|
* OBJECT and never told the markdown layout, so every run authored its own: a `## Entries` section
|
|
65
65
|
* with one `### UC-xx — title` subsection per use case, each holding a vertical `| Field | Value |`
|
|
66
66
|
* table. Under this spec that parses as no table at all — and the vertical layout carries no
|
|
@@ -68,8 +68,8 @@ export const SCOPE_CONTRACT = {
|
|
|
68
68
|
* reports as perfectly readable while yielding nothing.
|
|
69
69
|
*
|
|
70
70
|
* Measured across every completed run in the corpus: 9 of 9 committed wiring maps parsed to ZERO
|
|
71
|
-
* entries with `unreadableReason() === null`, and
|
|
72
|
-
*
|
|
71
|
+
* entries with `unreadableReason() === null`, and the wiring check certified green with 0/0 engines
|
|
72
|
+
* reached against a deliverable whose engines were on disk — the exact failure quoted
|
|
73
73
|
* in the banner above, recurred in a shape its own fix could not detect.
|
|
74
74
|
*
|
|
75
75
|
* The horizontal `## Wiring` table stays canonical: it is what `renderContract` emits, so a map
|
|
@@ -161,7 +161,7 @@ export function coerce(raw) {
|
|
|
161
161
|
// These contracts are MARKDOWN on disk, and a code span is the idiomatic way to write a path or
|
|
162
162
|
// an identifier in one; this repo's own prose backticks every path it names. Read literally,
|
|
163
163
|
// `` `src/capture/add.js` `` is a filename with two backticks in it, which is on no disk
|
|
164
|
-
// anywhere — so
|
|
164
|
+
// anywhere — so the wiring check reported "engine file not on disk" and then "reachability is not
|
|
165
165
|
// demonstrated" for a wiring map whose engines all resolve AND all reach the entry point. The
|
|
166
166
|
// gate that exists so no engine ships orphaned failing CLOSED, on a correct map, is the same
|
|
167
167
|
// silent-format family as the parser defects above, arriving from the other direction.
|
|
@@ -222,7 +222,7 @@ export function uncoerce(v) {
|
|
|
222
222
|
* Split a `[a, b]` list body on commas that are NOT inside quotes.
|
|
223
223
|
*
|
|
224
224
|
* The old implementation was `body.split(",")`, and it shredded any member carrying a
|
|
225
|
-
* comma — even a correctly quoted one. Measured: a
|
|
225
|
+
* comma — even a correctly quoted one. Measured: a scope-contract run probed the running CLI,
|
|
226
226
|
* confirmed `tag` was unimplemented, and wrote the honest entry its own SKILL.md asks for —
|
|
227
227
|
* ["TBD — `tag` is not in dispatch.js's TABLE (exits 1, confirmed against the running CLI). A
|
|
228
228
|
* fixture asserting the spec'd behaviour (attach/remove a tag, idempotent double-tag) can only
|
|
@@ -324,7 +324,7 @@ export function splitFrontmatter(md) {
|
|
|
324
324
|
// An indented run beneath a key is a YAML BLOCK SEQUENCE, and it used to be skipped
|
|
325
325
|
// entirely — so `e2e_verification_fixtures:` followed by two `- "node …"` lines parsed to
|
|
326
326
|
// null, the members vanished, and no reader could tell "declared nothing" from "declared
|
|
327
|
-
// something I discarded". Measured: a scope-
|
|
327
|
+
// something I discarded". Measured: a scope-contract run wrote three scopes of researched
|
|
328
328
|
// fixtures in this form and every one evaporated. It is ACCEPTED now, because it is the form a
|
|
329
329
|
// model reaches for by default and the one that carries prose members without the quoting
|
|
330
330
|
// problem — and anything indented that is NOT a block sequence is reported rather than dropped.
|
|
@@ -683,39 +683,6 @@ export function readContract(path, spec = SCOPE_CONTRACT) {
|
|
|
683
683
|
return null;
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
-
/**
|
|
687
|
-
* Read every contract in a directory, markdown and legacy JSON alike.
|
|
688
|
-
*
|
|
689
|
-
* A `.md` and a `.json` for the same id is a half-finished migration, not two scopes — the
|
|
690
|
-
* markdown wins and the JSON is ignored, so a stale leftover cannot resurrect an old substrate.
|
|
691
|
-
*
|
|
692
|
-
* @param {string} dir - Directory of contracts (typically `scopes/`).
|
|
693
|
-
* @param {{tables:Object<string,string>}} [spec=SCOPE_CONTRACT] - Field → heading map.
|
|
694
|
-
* @returns {Array<{contract:Object, path:string, format:string, id:string}>} One entry per
|
|
695
|
-
* contract, sorted by id; [] when the directory is absent. Unparseable files are skipped.
|
|
696
|
-
*/
|
|
697
|
-
export function readAllContracts(dir, spec = SCOPE_CONTRACT) {
|
|
698
|
-
if (!existsSync(dir)) return [];
|
|
699
|
-
let names;
|
|
700
|
-
try { names = readdirSync(dir); } catch { return []; }
|
|
701
|
-
const ids = new Map();
|
|
702
|
-
for (const f of names) {
|
|
703
|
-
const m = f.match(/^(.+)\.(md|json)$/);
|
|
704
|
-
if (!m) continue;
|
|
705
|
-
// markdown wins over a legacy sibling of the same id
|
|
706
|
-
if (m[2] === "json" && ids.has(m[1])) continue;
|
|
707
|
-
if (m[2] === "md" || !ids.has(m[1])) ids.set(m[1], f);
|
|
708
|
-
}
|
|
709
|
-
const out = [];
|
|
710
|
-
for (const [id, file] of [...ids].sort(([a], [b]) => a.localeCompare(b))) {
|
|
711
|
-
try {
|
|
712
|
-
const found = readContract(join(dir, file), spec);
|
|
713
|
-
if (found) out.push({ ...found, id });
|
|
714
|
-
} catch { /* an unparseable contract is reported by spec-lint, not here */ }
|
|
715
|
-
}
|
|
716
|
-
return out;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
686
|
// ---------------------------------------------------------------------------
|
|
720
687
|
// The scope ↔ spec join
|
|
721
688
|
// ---------------------------------------------------------------------------
|
|
@@ -734,35 +701,6 @@ export function ucId(ref) {
|
|
|
734
701
|
return String(ref ?? "").trim().replace(/^\[\[|\]\]$/g, "").replace(/^usecases\//, "").replace(/\.md$/, "").trim();
|
|
735
702
|
}
|
|
736
703
|
|
|
737
|
-
/**
|
|
738
|
-
* Normalise one requirement reference to the registry's key space (`REQ-<n>`).
|
|
739
|
-
*
|
|
740
|
-
* TWO KEY SPACES FOR ONE THING, and the measurement is what decided this. A pitch numbers its
|
|
741
|
-
* requirements `R1…R21`; the registry, every AC's `covers:` clause and every `traces_to[]` key off
|
|
742
|
-
* `REQ-<n>`. Measured across two runs of one pitch, the scope contracts cited `R<n>` — 20 of 21
|
|
743
|
-
* requirements had a scope claiming them, and every one of those links resolved to nothing,
|
|
744
|
-
* reported 23 times a run as a shape warning nobody could act on. So the edge WAS produced; it was
|
|
745
|
-
* severed by spelling alone.
|
|
746
|
-
*
|
|
747
|
-
* Normalising here rather than teaching the planner to emit `REQ-<n>` is deliberate: `covers[]` is
|
|
748
|
-
* an OPTIONAL contract field, so a fix that depends on a planner choosing to comply converts
|
|
749
|
-
* whatever links the next run happens to write, while this converts the links on contracts already
|
|
750
|
-
* committed, with no worker behaviour change. The craft still asks for `REQ-<n>` going forward.
|
|
751
|
-
*
|
|
752
|
-
* THIS IS A MAPPING PERFORMED BEFORE THE PATTERN, NOT A LOOSENING OF IT. `^REQ-[0-9]+$` is
|
|
753
|
-
* unchanged everywhere it appears; a reference this function does not recognise is returned
|
|
754
|
-
* verbatim, so it still fails that pattern and is still reported.
|
|
755
|
-
*
|
|
756
|
-
* @param {string} ref - A requirement reference (`REQ-12`, `R12`, `R-12`, `[[REQ-12]]`, any case).
|
|
757
|
-
* @returns {string} The canonical `REQ-<n>` id, or the trimmed input unchanged when it is not a
|
|
758
|
-
* numbered requirement reference at all.
|
|
759
|
-
*/
|
|
760
|
-
export function reqId(ref) {
|
|
761
|
-
const s = String(ref ?? "").trim().replace(/^\[\[|\]\]$/g, "").trim();
|
|
762
|
-
const m = s.match(/^(?:REQ|R)-?([0-9]+)$/i);
|
|
763
|
-
return m ? `REQ-${m[1]}` : s;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
704
|
/**
|
|
767
705
|
* The tasks on a LOCAL board that belong to a scope, joined through the COMMITTED spec.
|
|
768
706
|
*
|
|
@@ -809,28 +747,6 @@ function byUseCase(tasks, contract) {
|
|
|
809
747
|
return (tasks || []).filter((t) => (t.use_case_refs || []).some((r) => mine.has(ucId(r))));
|
|
810
748
|
}
|
|
811
749
|
|
|
812
|
-
/**
|
|
813
|
-
* Tasks that more than one scope would build — the ambiguity {@link tasksForScope} cannot resolve.
|
|
814
|
-
*
|
|
815
|
-
* Dispatch has to be a partition: a task built by two scopes is built twice, and the second builder
|
|
816
|
-
* is denied by the sandbox for writing outside its own substrate, so the cost lands as burnt
|
|
817
|
-
* attempts and escalations rather than as corruption. Measured on the corpus's four-scope,
|
|
818
|
-
* one-use-case cut: every task claimed by every scope.
|
|
819
|
-
*
|
|
820
|
-
* @param {Array<{id:string, scope_id?:string, use_case_refs?:string[]}>} tasks - The parsed board.
|
|
821
|
-
* @param {Array<{scope_id:string, use_cases?:string[]}>} contracts - Every scope in the run.
|
|
822
|
-
* @returns {Array<{task_id:string, scopes:string[]}>} One entry per contested task; [] when the
|
|
823
|
-
* assignment is already a partition.
|
|
824
|
-
*/
|
|
825
|
-
export function scopePartitionConflicts(tasks, contracts) {
|
|
826
|
-
const out = [];
|
|
827
|
-
for (const t of tasks || []) {
|
|
828
|
-
const owners = (contracts || []).filter((c) => tasksForScope([t], c).length).map((c) => c.scope_id);
|
|
829
|
-
if (owners.length > 1) out.push({ task_id: t.id, scopes: owners });
|
|
830
|
-
}
|
|
831
|
-
return out;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
750
|
/**
|
|
835
751
|
* Extract the prose a regeneration must not destroy — everything in the body except the sections
|
|
836
752
|
* this spec owns.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// MODEL FLOOR AND LAUNCH-RECORD CHECK — what a run may be launched with, decided in code.
|
|
2
|
+
//
|
|
3
|
+
// The model floor is a denylist of cheap tiers refused for the roles that write code and grade it
|
|
4
|
+
// (`exec`, `eval`). It is a denylist on purpose, and fails open: an id this list has never heard of
|
|
5
|
+
// is allowed, because refusing an unknown future model would stop a run for a reason nobody can
|
|
6
|
+
// act on. `qa` is not checked — a hunt discovers and never decides.
|
|
7
|
+
//
|
|
8
|
+
// WHY THE KERNEL OWNS IT. A rule that only a launch script applied was a rule a different launch
|
|
9
|
+
// path never met. Here it is one function, read by whoever opens the run and by the loop that
|
|
10
|
+
// drives it, so the two cannot disagree about what is below the floor.
|
|
11
|
+
|
|
12
|
+
const BELOW_FLOOR = [/ha[i1]ku/i, /\binstant\b/i, /\bmini\b/i, /\bnano\b/i, /\blite\b/i, /\bsmall\b/i];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether a model id names a tier below the floor.
|
|
16
|
+
* @param {*} m - A model id or alias.
|
|
17
|
+
* @returns {boolean} True for an empty value or a denylisted tier.
|
|
18
|
+
*/
|
|
19
|
+
export function belowFloor(m) {
|
|
20
|
+
const s = String(m ?? "").trim();
|
|
21
|
+
return s === "" || BELOW_FLOOR.some((re) => re.test(s));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The auto levels a launch may name. */
|
|
25
|
+
export const AUTO_LEVELS = ["interactive", "auto", "unattended"];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The roles whose model sits below the floor, as sentences naming the role. `qa` is never checked, and
|
|
29
|
+
* `eval` is not under `--no-eval`: a run that skips evaluation never resolves a judge.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} models - The role -> model matrix (`exec`, `eval`, `qa`).
|
|
32
|
+
* @param {{noEval?: boolean}} [opts] - Whether evaluation is skipped.
|
|
33
|
+
* @returns {string[]} One sentence per offending role; empty when the matrix clears the floor.
|
|
34
|
+
*/
|
|
35
|
+
export function floorProblems(models, { noEval = false } = {}) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const role of ["exec", "eval"]) {
|
|
38
|
+
if (role === "eval" && noEval) continue;
|
|
39
|
+
if (belowFloor(models?.[role])) {
|
|
40
|
+
const m = String(models?.[role] ?? "").trim();
|
|
41
|
+
out.push(`models.${role} ${m ? `"${m}" ` : ""}is below the model floor (sonnet or above)`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Everything wrong with a launch record, as sentences. Empty means launchable.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} a - The parsed launch record (`run-args.json`).
|
|
51
|
+
* @returns {string[]} Problems, empty when the record may run.
|
|
52
|
+
*/
|
|
53
|
+
export function launchProblems(a) {
|
|
54
|
+
const problems = [];
|
|
55
|
+
for (const k of ["slug", "autoLevel", "models", "budgets"]) {
|
|
56
|
+
if (!a || a[k] === undefined || a[k] === null || a[k] === "") problems.push(`launch record is missing "${k}"`);
|
|
57
|
+
}
|
|
58
|
+
if (a?.autoLevel && !AUTO_LEVELS.includes(a.autoLevel)) problems.push(`autoLevel must be one of ${AUTO_LEVELS.join(" | ")}`);
|
|
59
|
+
if (a?.budgets && !(a.budgets.maxRounds && a.budgets.attemptBudget)) {
|
|
60
|
+
problems.push("budgets must carry maxRounds and attemptBudget");
|
|
61
|
+
}
|
|
62
|
+
if (a?.models) problems.push(...floorProblems(a.models, { noEval: Boolean(a.noEval) }));
|
|
63
|
+
return problems;
|
|
64
|
+
}
|
package/kernel/lib/paths.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// syntaxes (a string literal and a `join(cwd, "…", …)` chain) is a rename that no single search
|
|
6
6
|
// completes.
|
|
7
7
|
//
|
|
8
|
-
// TIER DISCIPLINE
|
|
8
|
+
// TIER DISCIPLINE, the reason there are two roots:
|
|
9
9
|
// SHARED (committed) — prose a teammate reads: shaping, spec, contracts, requirements, report.
|
|
10
10
|
// LOCAL (gitignored) — run state, envelopes, verification artifacts, machine policy.
|
|
11
11
|
//
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
// touching no filesystem. The run-key half reads receipts and fails open (`null`, never a throw),
|
|
18
18
|
// because hooks call it and telemetry must never break a tool call.
|
|
19
19
|
|
|
20
|
-
import { join } from "node:path";
|
|
20
|
+
import { join, resolve, sep } from "node:path";
|
|
21
21
|
import { readFileSync } from "node:fs";
|
|
22
22
|
import { createHash } from "node:crypto";
|
|
23
23
|
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
|
-
// The two roots. Renaming a root is these two lines
|
|
25
|
+
// The two roots. Renaming a root is these two lines.
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
|
|
28
28
|
// Both roots are POSIX strings, deliberately. They are used two ways — fed to `join()` (which
|
|
@@ -35,7 +35,7 @@ import { createHash } from "node:crypto";
|
|
|
35
35
|
*
|
|
36
36
|
* Was `docs/shapeup-sdlc`. Moved out of `docs/` because many projects publish that directory
|
|
37
37
|
* through a static-site generator, which either publishes the spec tree by accident or fails the
|
|
38
|
-
* site build on it
|
|
38
|
+
* site build on it.
|
|
39
39
|
*/
|
|
40
40
|
export const SHARED = "shapeup";
|
|
41
41
|
|
|
@@ -43,8 +43,8 @@ export const SHARED = "shapeup";
|
|
|
43
43
|
export const LOCAL = ".shapeup";
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* The
|
|
47
|
-
* `bin/init.mjs` writes ignore rules covering both so a
|
|
46
|
+
* The earlier roots. Nothing migrates a project off them any more;
|
|
47
|
+
* `bin/init.mjs` writes ignore rules covering both so a checkout still carrying one cannot commit a
|
|
48
48
|
* run trace. Nothing else should read these.
|
|
49
49
|
*/
|
|
50
50
|
export const LEGACY = { shared: "docs/shapeup-sdlc", local: ".shapeup-sdlc" };
|
|
@@ -81,6 +81,18 @@ export const sharedRoot = (cwd, slug) => join(cwd, SHARED, slug);
|
|
|
81
81
|
* @param {string} slug - Feature slug.
|
|
82
82
|
* @returns {string} `<cwd>/.shapeup/<slug>`.
|
|
83
83
|
*/
|
|
84
|
+
/**
|
|
85
|
+
* Whether a working directory is inside a run trace (a `.shapeup/` folder).
|
|
86
|
+
*
|
|
87
|
+
* A kernel command resolves every path from the working directory, so run from inside the trace it
|
|
88
|
+
* would open a second, empty run nested in the first and write its records there — a real run did
|
|
89
|
+
* exactly that. The commands that write run state refuse instead.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} cwd - The directory the command was run from.
|
|
92
|
+
* @returns {boolean} True when `.shapeup` is one of its path segments.
|
|
93
|
+
*/
|
|
94
|
+
export const insideRunTrace = (cwd) => resolve(cwd).split(sep).includes(LOCAL);
|
|
95
|
+
|
|
84
96
|
export const localRoot = (cwd, slug) => join(cwd, LOCAL, slug);
|
|
85
97
|
|
|
86
98
|
// ---------------------------------------------------------------------------
|
|
@@ -93,7 +105,7 @@ export const specDir = (cwd, slug) => join(sharedRoot(cwd, slug), "spec");
|
|
|
93
105
|
export const usecasesDir = (cwd, slug) => join(specDir(cwd, slug), "usecases");
|
|
94
106
|
/** Shaping artifacts — pitch, framing, breadboard, baseline, glossary. `init run` looks here for a breadboard. */
|
|
95
107
|
export const shapingDir = (cwd, slug) => join(sharedRoot(cwd, slug), "shaping");
|
|
96
|
-
// The three contracts are markdown on disk and JSON on the wire
|
|
108
|
+
// The three contracts are markdown on disk and JSON on the wire — see
|
|
97
109
|
// `lib/contract.mjs`. `readContract()` accepts either extension, so a project mid-migration
|
|
98
110
|
// keeps working; these builders name the form the harness WRITES.
|
|
99
111
|
|
|
@@ -103,20 +115,18 @@ export const scopesDir = (cwd, slug) => join(sharedRoot(cwd, slug), "scopes");
|
|
|
103
115
|
export const scopeContract = (cwd, slug, id) => join(scopesDir(cwd, slug), `${id}.md`);
|
|
104
116
|
/** The wiring map — engine → seam → entry-point call site → affordance. */
|
|
105
117
|
export const wiringMap = (cwd, slug) => join(sharedRoot(cwd, slug), "wiring-map.md");
|
|
106
|
-
/** Archetype + entry_point
|
|
118
|
+
/** Archetype + entry_point + the build and launch probes. */
|
|
107
119
|
export const projectProfile = (cwd, slug) => join(sharedRoot(cwd, slug), "project-profile.md");
|
|
108
120
|
/** The REQ clause registry that covers-closure checks against. */
|
|
109
121
|
export const requirements = (cwd, slug) => join(sharedRoot(cwd, slug), "requirements.md");
|
|
110
|
-
/** Hill shards — mechanical phase per scope. */
|
|
111
|
-
export const hillDir = (cwd, slug) => join(sharedRoot(cwd, slug), "hill");
|
|
112
122
|
/** The frozen ship report, written once at GATE L4. */
|
|
113
123
|
export const report = (cwd, slug) => join(sharedRoot(cwd, slug), "REPORT.md");
|
|
114
|
-
/** Team-shared coaching rules, read back by the coachable workers and
|
|
124
|
+
/** Team-shared coaching rules, read back by the coachable workers, the plan activity and the loop at GATE L0. */
|
|
115
125
|
export const knowledgeBaseDir = (cwd) => join(sharedDir(cwd), "knowledge-base");
|
|
116
|
-
/** One
|
|
126
|
+
/** One reader's coaching file (a coachable worker, or the `plan` / `building` activity). */
|
|
117
127
|
export const knowledgeBase = (cwd, skill) => join(knowledgeBaseDir(cwd), `${skill}.md`);
|
|
118
128
|
// `.harness-version` and `.harness-migrations` were resolved here, committed by necessity as
|
|
119
|
-
//
|
|
129
|
+
// The third exception to the tier rule. Their sole writer was the upgrade path's data-migration runner, which
|
|
120
130
|
// has been removed; no code in the plugin has written either file since. The helpers went with
|
|
121
131
|
// them rather than remaining as resolvable paths to files nothing produces — a path helper is a
|
|
122
132
|
// claim that the file is part of the layout, and this one would have been false.
|
|
@@ -142,22 +152,18 @@ export const intake = (cwd, slug) => join(localRoot(cwd, slug), "intake.md");
|
|
|
142
152
|
export const breadboard = (cwd, slug) => join(localRoot(cwd, slug), "breadboard.md");
|
|
143
153
|
/**
|
|
144
154
|
* The launch record's filename, as a constant rather than a literal at each call site — the
|
|
145
|
-
* writer (`harness init run
|
|
155
|
+
* writer (`harness init run`) and every reader resolve
|
|
146
156
|
* the same path through this constant, the same way {@link runIdFromRoot} resolves `RECEIPT_FILE`
|
|
147
157
|
* against a bare run root rather than through {@link receipt}'s `(cwd, slug)` form: a caller that
|
|
148
158
|
* already holds the run root (an archived trace, `--run-root <dir>`) has no slug to reconstruct.
|
|
149
159
|
*/
|
|
150
160
|
export const RUN_ARGS_FILE = "run-args.json";
|
|
151
|
-
/** The launch record — the RunArgs object a run was launched with (GATE L0.9b),
|
|
161
|
+
/** The launch record — the RunArgs object a run was launched with (GATE L0.9b), written by `init run`. */
|
|
152
162
|
export const runArgsPath = (cwd, slug) => join(localRoot(cwd, slug), RUN_ARGS_FILE);
|
|
153
163
|
/** The run ledger — rounds, decisions, status frontmatter. */
|
|
154
164
|
export const harnessRun = (cwd, slug) => join(localRoot(cwd, slug), "harness-run.md");
|
|
155
|
-
/** File-derived mid-run digest, frozen by `reduce snapshot --write` as an audit anchor. */
|
|
156
|
-
export const runSnapshot = (cwd, slug) => join(localRoot(cwd, slug), "run-snapshot.json");
|
|
157
165
|
/** The task board directory. */
|
|
158
166
|
export const tasksDir = (cwd, slug) => join(localRoot(cwd, slug), "tasks");
|
|
159
|
-
/** The board index. */
|
|
160
|
-
export const boardIndex = (cwd, slug) => join(tasksDir(cwd, slug), "_index.md");
|
|
161
167
|
/** Compiled WorkOrders. */
|
|
162
168
|
export const ordersDir = (cwd, slug) => join(localRoot(cwd, slug), "orders");
|
|
163
169
|
/** Returned WorkResults. */
|
|
@@ -167,7 +173,7 @@ export const resultsDir = (cwd, slug) => join(localRoot(cwd, slug), "results");
|
|
|
167
173
|
*
|
|
168
174
|
* IT HAS ITS OWN DIRECTORY, and that is not tidiness. A receipt filed as a sibling of the order it
|
|
169
175
|
* answers (`orders/orient.receipt.json`) would land inside a directory three readers enumerate —
|
|
170
|
-
* `probe resume
|
|
176
|
+
* `probe resume` and the order set both `readdirSync` `orders/` — where it survives
|
|
171
177
|
* today only because each of them happens to filter `.json`. `orient.receipt.json` passes that
|
|
172
178
|
* filter and becomes a bogus `Order` node in the run graph. A separate directory means no future
|
|
173
179
|
* reader has to know this file exists.
|
|
@@ -180,27 +186,6 @@ export const resultsDir = (cwd, slug) => join(localRoot(cwd, slug), "results");
|
|
|
180
186
|
* (`receipt`, above, is the RUN receipt — a different fact, hence the different name.)
|
|
181
187
|
*/
|
|
182
188
|
export const dispatchReceipts = (cwd, slug) => join(localRoot(cwd, slug), "receipts", "dispatch.jsonl");
|
|
183
|
-
/**
|
|
184
|
-
* Leg-completion rows — the moment a dispatched leg was closed by the reducer.
|
|
185
|
-
*
|
|
186
|
-
* WHY IT IS A SEPARATE LEDGER FROM {@link dispatchReceipts}. A dispatch receipt is written by a
|
|
187
|
-
* `PostToolUse` hook, which fires when the skill RESOLVES — measured across archived runs, 1.8 to
|
|
188
|
-
* 47 seconds after the order was compiled, on legs that then ran for minutes. So the receipt is a
|
|
189
|
-
* hook-attested START and there is no end anywhere in the record set: concurrency and span are
|
|
190
|
-
* guesses without one. This ledger is the end, written by the one component that closes a leg.
|
|
191
|
-
*
|
|
192
|
-
* WHY IT SNAPSHOTS RATHER THAN POINTS. A row carries the order's `compiled_at` as a VALUE, not a
|
|
193
|
-
* path to read it from later. Order paths are reused verbatim on relaunch, so the `compiled_at` in
|
|
194
|
-
* `orders/<id>.json` is the LAST compile's; a row that deferred to the file would silently re-date
|
|
195
|
-
* itself to a dispatch that is not the one it recorded.
|
|
196
|
-
*
|
|
197
|
-
* APPEND-ONLY JSONL, for the same two reasons `dispatch.jsonl` is: order ids repeat across
|
|
198
|
-
* relaunches and rounds, and scopes close concurrently. One `O_APPEND` line needs no lock.
|
|
199
|
-
*
|
|
200
|
-
* A LEG THAT DIES LEAVES NO ROW, deliberately. The absence is the fact — a reader compares starts
|
|
201
|
-
* to completions and reports the hole rather than averaging over it.
|
|
202
|
-
*/
|
|
203
|
-
export const legLedger = (cwd, slug) => join(localRoot(cwd, slug), "legs.jsonl");
|
|
204
189
|
/** T0 verification artifacts. */
|
|
205
190
|
export const t0Dir = (cwd, slug) => join(localRoot(cwd, slug), "t0");
|
|
206
191
|
/** Immutable per-attempt verdict artifacts the evaluator must cite. */
|
|
@@ -216,45 +201,22 @@ export const trials = (cwd, slug) => join(t0Dir(cwd, slug), "trials.jsonl");
|
|
|
216
201
|
* {@link decisions}, one small file with one writer.
|
|
217
202
|
*/
|
|
218
203
|
export const gates = (cwd, slug) => join(localRoot(cwd, slug), "gates.jsonl");
|
|
219
|
-
/**
|
|
220
|
-
* The round build gate's verdicts — one immutable artifact per gate run, `r<N>-t<T>.json`.
|
|
221
|
-
*
|
|
222
|
-
* A separate directory from `t0/`, on purpose. A T0 verdict is one scope's fixtures inside that
|
|
223
|
-
* scope's substrate; this is the FEATURE's build, launched the way the ledger's `run_cmd` and the
|
|
224
|
-
* profile's probes say a user would, and it belongs to no scope. Written by `harness verify build`,
|
|
225
|
-
* read by `reduce hill` (a green T0 in a round whose build is red moves no dot) and by
|
|
226
|
-
* `harness compile` (a red gate is the next round's bug list).
|
|
227
|
-
*/
|
|
228
|
-
export const roundBuildDir = (cwd, slug) => join(localRoot(cwd, slug), "build");
|
|
229
204
|
/** Evaluator output — report, evidence, verdict ledger. */
|
|
230
205
|
export const evaluationDir = (cwd, slug) => join(localRoot(cwd, slug), "evaluation");
|
|
231
|
-
/** The workflow launcher's run directory — `journal.jsonl` and the launch `result.json`. */
|
|
232
|
-
export const workflowRunDir = (cwd, slug) => join(localRoot(cwd, slug), "workflow-run");
|
|
233
206
|
/** QA hunt output. */
|
|
234
207
|
export const qaDir = (cwd, slug) => join(localRoot(cwd, slug), "qa");
|
|
235
208
|
/** Scout recon — code-surface map, spikes, discovered seed. */
|
|
236
209
|
export const orientDir = (cwd, slug) => join(localRoot(cwd, slug), "orient");
|
|
237
210
|
/** Time-boxed spike workspace. */
|
|
238
211
|
export const spikesDir = (cwd, slug) => join(localRoot(cwd, slug), "spikes");
|
|
239
|
-
/** Covers-closure + reachability run trace. */
|
|
240
|
-
export const traceDir = (cwd, slug) => join(localRoot(cwd, slug), "trace");
|
|
241
212
|
/** The discovered-task ledger every discovery flow appends to. */
|
|
242
213
|
export const discoveryLedger = (cwd, slug) => join(localRoot(cwd, slug), "discovery", "ledger.md");
|
|
243
|
-
/**
|
|
244
|
-
* Adjudicated decisions, read back by `compile-order` as binding precedent.
|
|
245
|
-
*
|
|
246
|
-
* LOCAL since ADR-0001. It was committed, and it is appended to DURING a build round — so a run
|
|
247
|
-
* left the working tree dirty in the deliverable tier while it was still building. Its
|
|
248
|
-
* conclusions reach the team in `REPORT.md` at GATE L4 instead, frozen once.
|
|
249
|
-
*/
|
|
250
|
-
export const roundLedger = (cwd, slug) => join(localRoot(cwd, slug), "round-ledger.md");
|
|
251
|
-
|
|
252
214
|
/**
|
|
253
215
|
* Spec WORKING NOTES — analysis that informed the contract but is not the contract.
|
|
254
216
|
*
|
|
255
217
|
* `synthesis.md`, `assess-report.md`, `feedback.md`, `api-feasibility.md`, `integration.md`. The
|
|
256
|
-
* committed `spec/` keeps only what the evaluator grades against and a reviewer needs (
|
|
257
|
-
* "contract vs working artifact").
|
|
218
|
+
* committed `spec/` keeps only what the evaluator grades against and a reviewer needs (the
|
|
219
|
+
* "contract vs working artifact" rule).
|
|
258
220
|
*/
|
|
259
221
|
export const workingDir = (cwd, slug) => join(localRoot(cwd, slug), "working");
|
|
260
222
|
|
|
@@ -272,8 +234,7 @@ export const workingDir = (cwd, slug) => join(localRoot(cwd, slug), "working");
|
|
|
272
234
|
*
|
|
273
235
|
* What is left is written once, at run open, and never moved again, so it is not a concurrency
|
|
274
236
|
* hazard. Its readers all degrade rather than fail without it: the budget check and the snapshot
|
|
275
|
-
* both fall back to scanning for a receipt or a mid-run ledger
|
|
276
|
-
* instructions rather than guessing. The one thing it uniquely supplies is the slug that
|
|
237
|
+
* both fall back to scanning for a receipt or a mid-run ledger. The one thing it uniquely supplies is the slug that
|
|
277
238
|
* {@link resolveRunId} turns into a `run_id` for callers that were handed no slug — hook decision
|
|
278
239
|
* rows, above all, which without it are written unjoinable to any run.
|
|
279
240
|
*/
|
|
@@ -296,7 +257,7 @@ export const activeOrder = (cwd) => join(localDir(cwd), "active-order");
|
|
|
296
257
|
* close in the same act that retires the run pointers, and superseded by the next close.
|
|
297
258
|
*
|
|
298
259
|
* Why it exists: retiring the pointers is right (a finished run must stop fencing the checkout),
|
|
299
|
-
* but the close lands BEFORE the stretch that decides the most — the
|
|
260
|
+
* but the close lands BEFORE the stretch that decides the most — the stop census, the ship
|
|
300
261
|
* phase, the export — and every hook decision in that stretch resolved its run through the pointer
|
|
301
262
|
* that was just removed. Measured on a live run: every decision row after `closed_at` carried
|
|
302
263
|
* `run_id: null`, the census dispatch included. The breadcrumb keeps that join without keeping the
|
|
@@ -305,30 +266,14 @@ export const activeOrder = (cwd) => join(localDir(cwd), "active-order");
|
|
|
305
266
|
export const lastRun = (cwd) => join(localDir(cwd), "last-run");
|
|
306
267
|
|
|
307
268
|
/**
|
|
308
|
-
*
|
|
309
|
-
* reads before it lets an answer set say `ship`. The
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
* record `ask`, because nothing on disk said so.
|
|
269
|
+
* The stop step's census as DATA — `{verdict, cut_list, …}` — the one artifact GATE L4's resolver
|
|
270
|
+
* reads before it lets an answer set say `ship`. The loop's printed H0/H1/H2 blocks are prose; this
|
|
271
|
+
* file is the same proposal, written by the same hand, in a shape a gate can read. Measured before
|
|
272
|
+
* it existed: a green census returned as the worker's report, and L4 could only record `ask`,
|
|
273
|
+
* because nothing on disk said so. (The file keeps its name from when a worker wrote it.)
|
|
314
274
|
*/
|
|
315
275
|
export const hammerCensus = (cwd, slug) => join(localRoot(cwd, slug), "reports", "hammer-census.json");
|
|
316
276
|
|
|
317
|
-
/**
|
|
318
|
-
* Where the run scripts are staged for launch, inside the project.
|
|
319
|
-
*
|
|
320
|
-
* The orchestrator's Workflow scripts ship with the plugin, which lives OUTSIDE the project — and
|
|
321
|
-
* the Workflow tool will only load a script it is already allowed to read, meaning the working
|
|
322
|
-
* directory or a directory the operator added. An install path therefore fails the launch outright,
|
|
323
|
-
* with no permission rule that can fix it: the grant that authorises the tool does not widen where
|
|
324
|
-
* it may read from. So ``harness init run`` copies the shipped scripts here, and the launch names
|
|
325
|
-
* this project-local path. LOCAL, because a staged copy is a build artifact of the run, not a
|
|
326
|
-
* source file anybody should review or commit.
|
|
327
|
-
*/
|
|
328
|
-
export const workflowsStage = (cwd) => join(localDir(cwd), "workflows");
|
|
329
|
-
|
|
330
|
-
/** The staged run script as the LAUNCH names it — project-relative, `/`-joined on every platform. */
|
|
331
|
-
export const globWorkflowsStage = (...parts) => [LOCAL, "workflows", ...parts].join("/");
|
|
332
277
|
/** Hook receipts — one row per evaluation, so `allow` carries evidence. */
|
|
333
278
|
export const decisions = (cwd) => join(localDir(cwd), "decisions.jsonl");
|
|
334
279
|
/** Human-authored safety escape hatch. LOCAL so no PR can widen another machine's envelope. */
|
|
@@ -336,9 +281,9 @@ export const safetyOverrides = (cwd) => join(localDir(cwd), "safety-overrides.js
|
|
|
336
281
|
/**
|
|
337
282
|
* Telemetry shards.
|
|
338
283
|
*
|
|
339
|
-
* LOCAL
|
|
284
|
+
* LOCAL. Committed, they put `process.env.HOSTNAME` — a person's laptop name —
|
|
340
285
|
* into the repository, and append-only JSONL in git only grows. The cost, stated plainly: with
|
|
341
|
-
* these local,
|
|
286
|
+
* these local, the shards are a personal record, and "is the KB flywheel working across the
|
|
342
287
|
* team?" is no longer answerable from the repo.
|
|
343
288
|
*/
|
|
344
289
|
export const metricsDir = (cwd) => join(localDir(cwd), "metrics");
|
|
@@ -346,28 +291,10 @@ export const metricsDir = (cwd) => join(localDir(cwd), "metrics");
|
|
|
346
291
|
export const metricsShard = (cwd, id = process.env.HOSTNAME || "local") =>
|
|
347
292
|
join(metricsDir(cwd), `${id}.jsonl`);
|
|
348
293
|
|
|
349
|
-
/**
|
|
350
|
-
* The default landing tier for exported run records — the analysis plane's staging area.
|
|
351
|
-
*
|
|
352
|
-
* DELIBERATELY LOCAL, and the trade-off is stated rather than dodged. The run trace it reads from
|
|
353
|
-
* (`.shapeup/<slug>/`) is regenerable and gets wiped per-slug; an export keyed by run id survives
|
|
354
|
-
* that, which is the failure this directory closes. What it does NOT do is cross a machine
|
|
355
|
-
* boundary: making it SHARED would put per-run structured data and a machine id back in the
|
|
356
|
-
* repository, which is exactly what ADR-0001 moved the metrics shards out of git to prevent. A
|
|
357
|
-
* cross-machine warehouse is therefore an explicit `--out <dir>` to a destination the operator
|
|
358
|
-
* owns, never a default that commits telemetry on their behalf.
|
|
359
|
-
*/
|
|
360
|
-
export const exportsDir = (cwd) => join(localDir(cwd), "exports");
|
|
361
|
-
|
|
362
|
-
/** One exported run's table directory, keyed by the run id (see {@link mintRunId}). */
|
|
363
|
-
export const exportRunDir = (cwd, runId) => join(exportsDir(cwd), String(runId ?? "unkeyed"));
|
|
364
|
-
/** Archived pitches. */
|
|
365
|
-
export const pitchArchiveDir = (cwd) => join(localDir(cwd), "pitch-archive");
|
|
366
|
-
|
|
367
294
|
/**
|
|
368
295
|
* Gate answer sets, in resolution order (first hit wins).
|
|
369
296
|
*
|
|
370
|
-
* ONE TIER ONLY
|
|
297
|
+
* ONE TIER ONLY. There used to be a third candidate — a COMMITTED
|
|
371
298
|
* `gate-answers.json`, auto-discovered with no flag — so a file with `preset: ci` pre-approved
|
|
372
299
|
* GATE L4 ship sign-off for everyone who pulled the repo, and the same filename meant "my
|
|
373
300
|
* personal lane" at one path and "team policy" at another. Consent is now per-machine by
|
|
@@ -426,7 +353,7 @@ export const globShared = (slug, ...parts) => [SHARED, slug, ...parts].join("/")
|
|
|
426
353
|
|
|
427
354
|
/**
|
|
428
355
|
* The coaching file a coachable worker reads, as a repo-relative path for the WorkOrder payload.
|
|
429
|
-
* @param {string} skill - A coachable worker name (`COACHABLE` in compile.mjs), or `
|
|
356
|
+
* @param {string} skill - A coachable worker name (`COACHABLE` in compile.mjs), or an activity name (`plan`, `building`).
|
|
430
357
|
* @returns {string} e.g. `shapeup/knowledge-base/task-executor.md`.
|
|
431
358
|
*/
|
|
432
359
|
export const relKnowledgeBase = (skill) => [SHARED, "knowledge-base", `${skill}.md`].join("/");
|