shapeup-sdlc 1.7.0 → 3.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 +5 -5
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +17 -3
- package/README.md +124 -103
- package/SECURITY.md +37 -30
- package/bin/init.mjs +77 -47
- package/bin/lib/grant.mjs +145 -0
- package/commands/build.md +7 -6
- package/commands/ship.md +29 -16
- package/commands/wire.md +1 -1
- package/hooks/dispatch-receipt.mjs +195 -0
- package/hooks/gate-intake.mjs +3 -3
- package/hooks/gate-zerowork.mjs +25 -24
- package/hooks/hooks.json +9 -48
- package/hooks/lib/decision.mjs +19 -8
- package/hooks/safety-spine.mjs +3 -3
- package/hooks/sandbox-guard.mjs +84 -40
- package/{skills/tech-lead/scripts/compile-order.mjs → kernel/compile.mjs} +283 -41
- package/{skills/tech-lead/scripts/gate-answers.mjs → kernel/gate.mjs} +58 -14
- package/kernel/harness.mjs +134 -0
- package/{skills/tech-lead/scripts/fit-check.mjs → kernel/init/fit.mjs} +15 -12
- package/{skills/tech-lead/scripts/init-run.mjs → kernel/init/run.mjs} +152 -31
- package/{skills/tech-lead/scripts → kernel}/lib/argv.mjs +52 -31
- package/{skills/tech-lead/scripts/lib/contract-md.mjs → kernel/lib/contract.mjs} +251 -7
- package/kernel/lib/paths.mjs +491 -0
- package/kernel/probe/concurrency.mjs +510 -0
- package/{skills/tech-lead/scripts/aegis-digest.mjs → kernel/probe/digest.mjs} +9 -10
- package/kernel/probe/eval.mjs +77 -0
- package/kernel/probe/leg.mjs +125 -0
- package/{skills/tech-lead/scripts/resume-state.mjs → kernel/probe/resume.mjs} +204 -63
- package/{skills/tech-lead/scripts → kernel/probe}/stats.mjs +93 -11
- package/kernel/probe/t0.mjs +66 -0
- package/{skills/ba-pitch-analyzer/scripts/board-derive.mjs → kernel/reduce/board.mjs} +99 -23
- package/kernel/reduce/graph.mjs +437 -0
- package/{skills/tech-lead/scripts/hill-derive.mjs → kernel/reduce/hill.mjs} +28 -9
- package/kernel/reduce/ingest.mjs +633 -0
- package/{hooks/slop-cleaner.mjs → kernel/reduce/leftovers.mjs} +40 -57
- package/{skills/tech-lead/scripts/ship-report.mjs → kernel/reduce/ship.mjs} +61 -13
- package/{skills/tech-lead/scripts/run-snapshot.mjs → kernel/reduce/snapshot.mjs} +21 -15
- package/{skills/spec-evaluator/scripts/verdict-ledger.mjs → kernel/reduce/verdict.mjs} +13 -7
- package/kernel/report/export.mjs +325 -0
- package/kernel/report/facts.mjs +347 -0
- package/{skills/tech-lead/scripts/budget-check.mjs → kernel/verify/budget.mjs} +15 -12
- package/kernel/verify/dispatch.mjs +114 -0
- package/{skills/tech-lead/scripts/validate-envelope.mjs → kernel/verify/envelope.mjs} +20 -15
- package/{skills/tech-lead/scripts/lib → kernel/verify}/ratchet-tree.mjs +36 -14
- package/kernel/verify/skills.mjs +125 -0
- package/kernel/verify/spec.mjs +559 -0
- package/{skills/tech-lead/scripts/t0-verify.mjs → kernel/verify/t0.mjs} +134 -24
- package/{skills/tech-lead/scripts/trace-lint.mjs → kernel/verify/trace.mjs} +26 -11
- package/oracles/http-oracle.mjs +1 -1
- package/oracles/process-oracle.mjs +1 -1
- package/oracles/snapshot-oracle.mjs +1 -1
- package/oracles/test-oracle.mjs +1 -1
- package/package.json +11 -9
- package/skills/ba-pitch-analyzer/SKILL.md +5 -5
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +1 -1
- package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +12 -7
- package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +6 -3
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +5 -4
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +23 -13
- package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +17 -12
- package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +17 -38
- package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +3 -0
- package/skills/ba-pitch-analyzer/references/contract-patterns.md +8 -4
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +2 -0
- package/skills/ba-pitch-analyzer/references/task-generation.md +2 -2
- package/skills/qa-edge-hunter/SKILL.md +7 -3
- package/skills/scope-architect/SKILL.md +40 -6
- package/skills/solution-architect/SKILL.md +19 -4
- package/skills/spec-evaluator/SKILL.md +12 -2
- package/skills/task-executor/SKILL.md +7 -6
- package/skills/tech-lead/SKILL.md +50 -38
- package/skills/tech-lead/references/gates.md +69 -20
- package/skills/tech-lead/references/protocol.md +832 -0
- package/skills/tech-lead/schemas/domain.schema.json +341 -66
- package/skills/tech-lead/schemas/work-order.schema.json +11 -2
- package/skills/tech-lead/schemas/work-result.schema.json +1 -1
- package/skills/tech-lead/workflows/shapeup-run.js +1313 -750
- package/hooks/anti-rationalization.mjs +0 -238
- package/hooks/compact-snapshot.mjs +0 -47
- package/hooks/gate-deadline.mjs +0 -147
- package/hooks/gate-l2.mjs +0 -161
- package/hooks/session-rehydrate.mjs +0 -108
- package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +0 -239
- package/skills/spec-evaluator/README.md +0 -93
- package/skills/tech-lead/README.md +0 -71
- package/skills/tech-lead/references/delegation.md +0 -261
- package/skills/tech-lead/references/hard-rules.md +0 -34
- package/skills/tech-lead/references/invocation.md +0 -45
- package/skills/tech-lead/references/ledger-schema.md +0 -213
- package/skills/tech-lead/references/round-protocol.md +0 -234
- package/skills/tech-lead/references/state-model.md +0 -66
- package/skills/tech-lead/scripts/ingest-result.mjs +0 -258
- package/skills/tech-lead/scripts/lib/is-main.mjs +0 -81
- package/skills/tech-lead/scripts/lib/paths.mjs +0 -280
- package/skills/tech-lead/scripts/run-workflow.mjs +0 -381
- package/skills/translator/README.md +0 -66
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// structurally invalid. No agent can fabricate this file's contents
|
|
8
8
|
// because it is produced by actually running the commands.
|
|
9
9
|
//
|
|
10
|
-
// Zero dependencies, zero network — same discipline as oracles/* and
|
|
10
|
+
// Zero dependencies, zero network — same discipline as oracles/* and the GATE L2 block.
|
|
11
11
|
//
|
|
12
12
|
// THE PAWL (v1.5). This script also owns the ratchet's comparison and its history. The attempt
|
|
13
13
|
// loop used to be a BUDGETED RETRY LOOP wearing a ratchet's shape: `computeVerdict` returned four
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
// N+1 build on attempt N instead of restarting from unexplained code.
|
|
30
30
|
//
|
|
31
31
|
// Usage:
|
|
32
|
-
// node "${CLAUDE_PLUGIN_ROOT}/
|
|
32
|
+
// node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" verify t0 <scope-contract.json> \
|
|
33
33
|
// --round N --attempt M [--cwd <dir>] [--out <dir>] [--seesaw-registry <path>] [--no-seesaw]
|
|
34
34
|
// [--no-ratchet]
|
|
35
35
|
//
|
|
@@ -39,11 +39,28 @@ import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync, rea
|
|
|
39
39
|
import { join, dirname } from "node:path";
|
|
40
40
|
import { spawnSync } from "node:child_process";
|
|
41
41
|
import { createHash } from "node:crypto";
|
|
42
|
-
import { digest } from "
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
42
|
+
import { digest } from "../probe/digest.mjs";
|
|
43
|
+
import { runArgs } from "../lib/argv.mjs";
|
|
44
|
+
import { snapshot, restore, keptRef } from "./ratchet-tree.mjs";
|
|
45
|
+
import { readContract, SCOPE_CONTRACT } from "../lib/contract.mjs";
|
|
46
|
+
import { runIdFromRoot, localRoot, SHARED } from "../lib/paths.mjs";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The feature slug a scope contract belongs to, from its path.
|
|
50
|
+
*
|
|
51
|
+
* Scope contracts live at `<SHARED>/<slug>/scopes/<id>.md`, so the slug is the segment two levels
|
|
52
|
+
* above the file. Derived rather than flagged because every caller already names the contract, and
|
|
53
|
+
* a second way to say the same thing is a second thing to get wrong.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} contractPath - Path to the scope contract, absolute or relative.
|
|
56
|
+
* @returns {string} The slug, or "" when the path does not have the expected shape — in which case
|
|
57
|
+
* the caller's `--out` is the only sensible answer and its absence is a usage error.
|
|
58
|
+
*/
|
|
59
|
+
export function slugFromContractPath(contractPath) {
|
|
60
|
+
const parts = String(contractPath).split(/[/\\]/);
|
|
61
|
+
const i = parts.lastIndexOf("scopes");
|
|
62
|
+
return i > 0 ? parts[i - 1] : "";
|
|
63
|
+
}
|
|
47
64
|
|
|
48
65
|
/**
|
|
49
66
|
* Run one shell command and capture its outcome (10-minute timeout).
|
|
@@ -71,7 +88,24 @@ function runCommand(cmd, cwd) {
|
|
|
71
88
|
*/
|
|
72
89
|
export function runFixtures(fixtures, cwd) {
|
|
73
90
|
const results = (fixtures || []).map((cmd) => runCommand(cmd, cwd));
|
|
74
|
-
|
|
91
|
+
// AN ABSENCE IS NOT A PASS, and this is the whole "measured, not claimed" invariant in one line.
|
|
92
|
+
//
|
|
93
|
+
// `[].every(...)` is `true`, so a scope whose contract declared no fixtures — or whose fixtures
|
|
94
|
+
// failed to PARSE, which is how it actually happened — scored `pass: true` and the verdict came
|
|
95
|
+
// back `overall: "green"`. Measured directly:
|
|
96
|
+
//
|
|
97
|
+
// runFixtures(undefined) -> pass=true, results=0
|
|
98
|
+
// computeVerdict(…) -> {"overall":"green"}
|
|
99
|
+
//
|
|
100
|
+
// Nothing ran, and the scope was certified. That is not a weaker version of the guarantee, it is
|
|
101
|
+
// the inverse of it: the one layer the evaluator is required to cite, green on zero evidence. The
|
|
102
|
+
// same reasoning the decision ledger states one directory over — "an absence proves nothing and
|
|
103
|
+
// must not sharpen the message".
|
|
104
|
+
//
|
|
105
|
+
// `ran` is reported rather than folded into `pass` so the two facts stay distinguishable: a scope
|
|
106
|
+
// with nothing to run is a contract that is not finished, which is different from a scope whose
|
|
107
|
+
// fixtures ran and failed, and the ratchet should be able to tell them apart.
|
|
108
|
+
return { pass: results.length > 0 && results.every((r) => r.pass), ran: results.length > 0, results };
|
|
75
109
|
}
|
|
76
110
|
|
|
77
111
|
/**
|
|
@@ -183,9 +217,43 @@ export function better(next, current) {
|
|
|
183
217
|
if (next.regressions !== current.regressions) return next.regressions < current.regressions;
|
|
184
218
|
if (next.fixtures_passed !== current.fixtures_passed) return next.fixtures_passed > current.fixtures_passed;
|
|
185
219
|
if (next.db_probe !== current.db_probe) return (next.db_probe ?? 0) > (current.db_probe ?? 0);
|
|
220
|
+
// EVERY COMPONENT TIES. What that means depends entirely on whether the incumbent was green.
|
|
221
|
+
//
|
|
222
|
+
// On a RED scope a tie is the sawtooth the pawl exists to stop, and it stays `false` — that is the
|
|
223
|
+
// case the rule above was written for and the case the guard fixtures assert (2/5 vs 2/5).
|
|
224
|
+
//
|
|
225
|
+
// On a GREEN scope there is no score left to win. Every further attempt ties BY CONSTRUCTION, so
|
|
226
|
+
// `false` restores the tree every time and the scope can never change again: the pawl stops being
|
|
227
|
+
// a pawl and becomes a weld. That is exactly the state a spec-conformance round is in. EVAL
|
|
228
|
+
// returns FAIL citing bugs T0's fixtures do not test — a bare stack trace, an error message that
|
|
229
|
+
// does not match the catalogue, validation ordered after a load — the next round's workers fix
|
|
230
|
+
// them, T0 scores 1/1 again because its fixtures never tested the prose, and the ratchet throws
|
|
231
|
+
// the fix away.
|
|
232
|
+
//
|
|
233
|
+
// Measured on a live run: round 2 produced six trials, 0 kept, 6 reverted, `bin/todo.js` byte-for-
|
|
234
|
+
// byte unchanged, and all three cited bugs reproducing. The FAIL → fix → re-evaluate loop — the
|
|
235
|
+
// harness's central promise — is inert for precisely the defects EVAL is best at finding.
|
|
236
|
+
//
|
|
237
|
+
// So: a green tie KEEPS. The tree may move when the score cannot, because by then the work being
|
|
238
|
+
// done is work T0 does not measure, and refusing it does not protect the scope — it freezes it.
|
|
239
|
+
if (isGreenScore(next) && isGreenScore(current)) return true;
|
|
186
240
|
return false;
|
|
187
241
|
}
|
|
188
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Is this score a clean pass — every fixture passing, none of them absent, no outstanding regression?
|
|
245
|
+
*
|
|
246
|
+
* `fixtures_total > 0` is load-bearing: a scope with no fixtures has nothing to be green ABOUT, and
|
|
247
|
+
* treating its empty score as a pass is the same absence-reads-as-success mistake `runFixtures`
|
|
248
|
+
* made one function above.
|
|
249
|
+
*
|
|
250
|
+
* @param {{regressions:number, fixtures_passed:number, fixtures_total:number}} s - A trial score.
|
|
251
|
+
* @returns {boolean} True when the score represents a real, complete pass.
|
|
252
|
+
*/
|
|
253
|
+
function isGreenScore(s) {
|
|
254
|
+
return s.regressions === 0 && s.fixtures_total > 0 && s.fixtures_passed === s.fixtures_total;
|
|
255
|
+
}
|
|
256
|
+
|
|
189
257
|
/**
|
|
190
258
|
* The one rule that replaces the protocol's two red branches.
|
|
191
259
|
*
|
|
@@ -327,7 +395,7 @@ function sha256(text) {
|
|
|
327
395
|
*
|
|
328
396
|
* An `existsSync` guard would be check-then-write: still racy, and still a policy expressed in code
|
|
329
397
|
* rather than a property of the store. `flag: "wx"` makes overwriting IMPOSSIBLE — the filesystem
|
|
330
|
-
* refuses with EEXIST and the loop moves to the next ordinal. Same class of move as
|
|
398
|
+
* refuses with EEXIST and the loop moves to the next ordinal. Same class of move as the entry-point guard:
|
|
331
399
|
* replace a fragile comparison with one that cannot silently be wrong.
|
|
332
400
|
*
|
|
333
401
|
* The evaluator's citation contract is unaffected: it re-hashes whatever path it is handed, and
|
|
@@ -364,7 +432,7 @@ export function writeArtifact(outDir, round, attempt, verdictBody) {
|
|
|
364
432
|
|
|
365
433
|
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
366
434
|
export const ARGV_SPEC = {
|
|
367
|
-
usage: "
|
|
435
|
+
usage: "harness.mjs verify t0 <scope-contract.json> --round N --attempt M [--cwd <dir>] [--out <dir>] " +
|
|
368
436
|
"[--seesaw-registry <path>] [--no-seesaw] [--no-ratchet]",
|
|
369
437
|
_: { arity: 1, max: 1, name: "scope-contract.json" },
|
|
370
438
|
round: { type: "int", min: 1, required: true },
|
|
@@ -377,25 +445,45 @@ export const ARGV_SPEC = {
|
|
|
377
445
|
};
|
|
378
446
|
|
|
379
447
|
/**
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* @
|
|
448
|
+
* Run the T0 evidence layer for one attempt and write the verdict artifact the judge cites.
|
|
449
|
+
*
|
|
450
|
+
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
451
|
+
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
452
|
+
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
383
453
|
*/
|
|
384
|
-
async function
|
|
385
|
-
const args = runArgs(ARGV_SPEC);
|
|
454
|
+
export async function cli(rawArgv) {
|
|
455
|
+
const args = runArgs(ARGV_SPEC, rawArgv);
|
|
386
456
|
const contractPath = args._[0];
|
|
387
|
-
// Markdown first, legacy JSON second (ADR-0001, lib/contract
|
|
457
|
+
// Markdown first, legacy JSON second (ADR-0001, lib/contract.mjs).
|
|
388
458
|
const found = readContract(contractPath, SCOPE_CONTRACT);
|
|
389
459
|
if (!found) { console.error(`t0-verify: no scope contract at ${contractPath} (.md or .json)`); process.exit(2); }
|
|
390
460
|
const contract = found.contract;
|
|
391
461
|
const cwd = args.cwd || process.cwd();
|
|
392
|
-
|
|
462
|
+
// WHERE THE VERDICT LANDS, and the one thing about this file that must not be derived from the
|
|
463
|
+
// contract's own location.
|
|
464
|
+
//
|
|
465
|
+
// This used to be `dirname(dirname(contractPath))` — "the parent of scopes/" — which was correct
|
|
466
|
+
// while scope contracts and the run trace shared a root. ADR-0001 split the tiers and moved scope
|
|
467
|
+
// contracts to COMMITTED `shapeup/<slug>/scopes/`, so the default silently followed them: every
|
|
468
|
+
// T0 verdict and trial row was written to `shapeup/<slug>/t0/` while `probe t0` — the ONLY reader,
|
|
469
|
+
// and the one the build round's confirm stage asks — resolves `verdictsDir()` to
|
|
470
|
+
// `.shapeup/<slug>/t0/`. AGENTS.md is unambiguous that T0 artifacts are LOCAL tier.
|
|
471
|
+
//
|
|
472
|
+
// Measured on a live run: six verdicts and six trial rows were written, `foundation` among them
|
|
473
|
+
// with `fixtures_passed: 2/2, status: kept` — a real green — and the round still reported ZERO
|
|
474
|
+
// green scopes, tripped the inner breaker and returned `gate_h`. The evidence existed, was
|
|
475
|
+
// correct, and was invisible to the only thing that reads it. That is the same silent disconnect
|
|
476
|
+
// `hooks/lib/decision.mjs` records in its own header, one directory over.
|
|
477
|
+
//
|
|
478
|
+
// `--out` still wins: `verify t0` is also called with an explicit run root, and that caller knows
|
|
479
|
+
// better than any derivation.
|
|
480
|
+
const outDir = args.out || localRoot(cwd, slugFromContractPath(contractPath));
|
|
393
481
|
const round = args.round;
|
|
394
482
|
const attempt = args.attempt;
|
|
395
483
|
|
|
396
484
|
const fixtures = runFixtures(contract.e2e_verification_fixtures, cwd);
|
|
397
485
|
const dbProbe = runDbProbe(contract.db_probe, cwd);
|
|
398
|
-
// --seesaw-registry is expected explicitly (tech-lead always passes it,
|
|
486
|
+
// --seesaw-registry is expected explicitly (tech-lead always passes it, protocol.md 3c);
|
|
399
487
|
// standalone CLI use without it simply skips the seesaw check rather than guessing a path.
|
|
400
488
|
const seesawRegistry = args.noSeesaw ? null : args.seesawRegistry || null;
|
|
401
489
|
const seesaw = args.noSeesaw || fixtures.pass === false
|
|
@@ -417,7 +505,13 @@ async function main() {
|
|
|
417
505
|
const crashed = fixtures.results.some((r) => r.error) || !!dbProbe?.error;
|
|
418
506
|
const { status, action } = decideStatus(verdictBetter, crashed);
|
|
419
507
|
|
|
508
|
+
// The run key, read from the receipt that lives in the run root this script was pointed at.
|
|
509
|
+
// `--out` IS that root, so identity comes from the receipt rather than from parsing a slug back
|
|
510
|
+
// out of a directory name; a caller that points `--out` somewhere else simply gets no key.
|
|
511
|
+
const runId = runIdFromRoot(outDir);
|
|
512
|
+
|
|
420
513
|
const { path, sha256: hash, trial } = writeArtifact(outDir, round, attempt, {
|
|
514
|
+
...(runId ? { run_id: runId } : {}),
|
|
421
515
|
scope_id: contract.scope_id,
|
|
422
516
|
fixtures: fixtures.results.map(({ cmd, exit, pass }) => ({ cmd, exit, pass })),
|
|
423
517
|
db_probe: dbProbe && { cmd: dbProbe.cmd, exit: dbProbe.exit, pass: dbProbe.pass },
|
|
@@ -431,18 +525,37 @@ async function main() {
|
|
|
431
525
|
// the pre-v1.5 behaviour, kept for standalone CLI use and for any caller managing its own tree.
|
|
432
526
|
let tree = { ok: false, reason: "--no-ratchet" };
|
|
433
527
|
if (!args.noRatchet) {
|
|
434
|
-
|
|
528
|
+
// The revert is bounded by what this scope was allowed to write. Unbounded, it rolls back every
|
|
529
|
+
// other scope building alongside it — see `restore`.
|
|
530
|
+
const own = contract.allowed_file_substrate || [];
|
|
531
|
+
tree = action === "keep" ? snapshot(contract.scope_id, cwd) : restore(contract.scope_id, cwd, own);
|
|
435
532
|
// First trial with nothing to restore to: there is no kept tree yet BY DEFINITION. Take one,
|
|
436
533
|
// so trial 2 has a floor to fall back to instead of inheriting the "no revert at all" defect.
|
|
437
|
-
|
|
534
|
+
// Gated on `!baseline` (no prior kept/rebased trial exists at all), NOT on `!tree.ok` alone —
|
|
535
|
+
// a genuine restore failure with a real baseline to have restored to must NOT be silently
|
|
536
|
+
// promoted as the new kept tree; it stays `tree.ok === false` so the trial row records
|
|
537
|
+
// `tree_ref: null` and the failure stays visible instead of quietly becoming the baseline.
|
|
538
|
+
if (action === "restore" && !tree.ok && !baseline) tree = { ...snapshot(contract.scope_id, cwd), fell_back_to: "snapshot" };
|
|
438
539
|
}
|
|
439
540
|
|
|
440
541
|
// `baseline_trial` is the parent link — the experiment DAG (lineage, PARENT_OF and a genuine
|
|
441
542
|
// SUPERSEDES edge) delivered as one field, with no graph store behind it.
|
|
442
|
-
|
|
543
|
+
//
|
|
544
|
+
// COUNTED WITHIN THE SCOPE, NEVER ACROSS THE FILE. This used to be
|
|
545
|
+
// `readTrials(trialsPath).length + 1` — every row in the run, whoever wrote it — which is a
|
|
546
|
+
// read-modify-write counter over a file that concurrent scopes append to. Scopes build in
|
|
547
|
+
// parallel, so several `verify t0` processes reach this line at once, each reads the same length
|
|
548
|
+
// and each writes the same ordinal: four concurrent scopes produced ordinals [1,1,1,4] and
|
|
549
|
+
// [1,1,3,3]. The ordinal is only ever consumed WITHIN a scope — `baseline` is picked from
|
|
550
|
+
// `priorTrials`, which is already filtered by `scope_id`, and the ratchet report groups by scope
|
|
551
|
+
// before it sorts — so counting the scope's own prior trials is both the race-free answer and the
|
|
552
|
+
// one the readers actually want. Two attempts of the SAME scope cannot race: one worker leg owns
|
|
553
|
+
// a scope and runs its attempts in sequence.
|
|
554
|
+
const trialNo = priorTrials.length + 1;
|
|
443
555
|
const row = {
|
|
444
556
|
schema_version: 1,
|
|
445
557
|
trial: trialNo,
|
|
558
|
+
...(runId ? { run_id: runId } : {}),
|
|
446
559
|
round, attempt,
|
|
447
560
|
scope_id: contract.scope_id,
|
|
448
561
|
at: new Date().toISOString(),
|
|
@@ -465,6 +578,3 @@ async function main() {
|
|
|
465
578
|
process.exit(verdict.overall === "green" ? 0 : 1);
|
|
466
579
|
}
|
|
467
580
|
|
|
468
|
-
if (isMain(import.meta.url)) {
|
|
469
|
-
main();
|
|
470
|
-
}
|
|
@@ -30,18 +30,17 @@
|
|
|
30
30
|
// Writes LOCAL .shapeup/<slug>/trace/report.json (regenerated each run)
|
|
31
31
|
// LOCAL .shapeup/<slug>/trace/wiring.mmd (Mermaid view of the checked graph)
|
|
32
32
|
//
|
|
33
|
-
// Zero dependencies, zero network — same discipline as
|
|
33
|
+
// Zero dependencies, zero network — same discipline as `harness verify t0` / `harness compile`.
|
|
34
34
|
//
|
|
35
|
-
// Usage: node
|
|
35
|
+
// Usage: node kernel/harness.mjs verify trace --slug <slug> [--cwd <dir>] [--gate] [--quiet]
|
|
36
36
|
// Exit: advisory (default) → always 0. --gate → 1 when overall is red.
|
|
37
37
|
|
|
38
38
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
39
39
|
import { resolve, join, dirname, relative, isAbsolute } from "node:path";
|
|
40
|
-
import { readBoard } from "
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import { readContract, unreadableReason, WIRING_MAP, PROJECT_PROFILE } from "./lib/contract-md.mjs";
|
|
40
|
+
import { readBoard } from "../compile.mjs";
|
|
41
|
+
import { runArgs } from "../lib/argv.mjs";
|
|
42
|
+
import { sharedRoot, traceDir, relLocal } from "../lib/paths.mjs";
|
|
43
|
+
import { readContract, unreadableReason, LEGACY_LAYOUT, WIRING_MAP, PROJECT_PROFILE } from "../lib/contract.mjs";
|
|
45
44
|
|
|
46
45
|
// --- requirements.md registry parser -----------------------------------------
|
|
47
46
|
// A committed markdown table: | REQ-id | clause (verbatim) | source | status | note |
|
|
@@ -273,6 +272,16 @@ export function traceLint(slug, { cwd, gate = false }) {
|
|
|
273
272
|
skipped_reason: `wiring-map.md is present but unreadable (${wiringUnreadable}) — reachability cannot be claimed.` };
|
|
274
273
|
}
|
|
275
274
|
|
|
275
|
+
// A map that parsed only through the migration reader still WORKS — but silence about that is
|
|
276
|
+
// how a temporary fallback becomes a permanent second format. Warn, so the next `wire` converges
|
|
277
|
+
// the file on the canonical table rather than the reader carrying the old shape indefinitely.
|
|
278
|
+
if (wiringMap && wiringMap[LEGACY_LAYOUT]) {
|
|
279
|
+
findings.push({ severity: "warn", code: "WIRING-LEGACY-LAYOUT", message:
|
|
280
|
+
"wiring-map.md is in the pre-canonical per-use-case layout (`### UC-xx` + a `| Field | Value |` table). " +
|
|
281
|
+
"It reads correctly through the migration reader, but the canonical form is one `## Wiring` table — " +
|
|
282
|
+
"re-run the wire operation to converge it, so this artifact stops having two shapes." });
|
|
283
|
+
}
|
|
284
|
+
|
|
276
285
|
if (wiringMap) {
|
|
277
286
|
let profileFound = null;
|
|
278
287
|
try { profileFound = readContract(profilePath, PROJECT_PROFILE); }
|
|
@@ -329,7 +338,7 @@ export function traceLint(slug, { cwd, gate = false }) {
|
|
|
329
338
|
// ---------------------------------------------------------------------------
|
|
330
339
|
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
331
340
|
export const ARGV_SPEC = {
|
|
332
|
-
usage: "
|
|
341
|
+
usage: "harness.mjs verify trace --slug <slug> [--cwd <dir>] [--gate] [--quiet]",
|
|
333
342
|
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
334
343
|
slug: { type: "str", required: true },
|
|
335
344
|
cwd: { type: "path" },
|
|
@@ -337,9 +346,15 @@ export const ARGV_SPEC = {
|
|
|
337
346
|
quiet: { type: "flag" },
|
|
338
347
|
};
|
|
339
348
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Run the traceability oracle over the spine artifacts and write its report.
|
|
351
|
+
*
|
|
352
|
+
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
353
|
+
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
354
|
+
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
355
|
+
*/
|
|
356
|
+
export async function cli(rawArgv) {
|
|
357
|
+
const args = runArgs(ARGV_SPEC, rawArgv);
|
|
343
358
|
const cwd = resolve(args.cwd || process.cwd());
|
|
344
359
|
const slug = args.slug;
|
|
345
360
|
const gate = !!args.gate;
|
package/oracles/http-oracle.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import { spawn } from "node:child_process";
|
|
|
34
34
|
import { createServer } from "node:net";
|
|
35
35
|
import { readFileSync } from "node:fs";
|
|
36
36
|
import { matchNum, toRegExp, formatReport } from "./_shared.mjs";
|
|
37
|
-
import { isMain } from "../
|
|
37
|
+
import { isMain } from "../kernel/lib/argv.mjs";
|
|
38
38
|
|
|
39
39
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
40
40
|
|
|
@@ -25,7 +25,7 @@ import { spawnSync } from "node:child_process";
|
|
|
25
25
|
import { mkdtempSync, writeFileSync, rmSync, readFileSync } from "node:fs";
|
|
26
26
|
import { tmpdir } from "node:os";
|
|
27
27
|
import { join } from "node:path";
|
|
28
|
-
import { isMain } from "../
|
|
28
|
+
import { isMain } from "../kernel/lib/argv.mjs";
|
|
29
29
|
|
|
30
30
|
const CRASH_RE = /at\s+.*:\d+:\d+|Traceback|panic:|unhandled|Segmentation fault/i;
|
|
31
31
|
|
|
@@ -28,7 +28,7 @@ import { spawnSync } from "node:child_process";
|
|
|
28
28
|
import { readFileSync, existsSync } from "node:fs";
|
|
29
29
|
import { dirname, isAbsolute, join } from "node:path";
|
|
30
30
|
import { CRASH_RE, formatReport } from "./_shared.mjs";
|
|
31
|
-
import { isMain } from "../
|
|
31
|
+
import { isMain } from "../kernel/lib/argv.mjs";
|
|
32
32
|
|
|
33
33
|
// Normalize so trailing spaces and final-newline differences never cause a spurious FAIL.
|
|
34
34
|
function normalize(s) {
|
package/oracles/test-oracle.mjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
import { spawnSync } from "node:child_process";
|
|
32
32
|
import { readFileSync } from "node:fs";
|
|
33
33
|
import { matchNum, toRegExp, formatReport } from "./_shared.mjs";
|
|
34
|
-
import { isMain } from "../
|
|
34
|
+
import { isMain } from "../kernel/lib/argv.mjs";
|
|
35
35
|
|
|
36
36
|
// Parse common test-runner output into { total, pass, fail, failing[] }.
|
|
37
37
|
function parseSuite(out) {
|
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shapeup-sdlc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Shape Up for coding agents \u2014 with gates the agent can't talk its way past. Harness for Claude Code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"shapeup-sdlc": "bin/init.mjs"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
+
".claude-plugin/",
|
|
10
|
+
".claude/settings.local.example.json",
|
|
11
|
+
".env.shapeup.example",
|
|
12
|
+
"AGENTS.md",
|
|
13
|
+
"SECURITY.md",
|
|
9
14
|
"bin/",
|
|
10
|
-
"skills/",
|
|
11
|
-
"hooks/",
|
|
12
15
|
"commands/",
|
|
16
|
+
"hooks/",
|
|
17
|
+
"kernel/",
|
|
13
18
|
"oracles/",
|
|
14
|
-
"
|
|
15
|
-
"AGENTS.md",
|
|
16
|
-
".env.shapeup.example",
|
|
17
|
-
".claude/settings.local.example.json",
|
|
18
|
-
"SECURITY.md"
|
|
19
|
+
"skills/"
|
|
19
20
|
],
|
|
20
21
|
"repository": {
|
|
21
22
|
"type": "git",
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"test": "node tests/structural.mjs",
|
|
36
|
-
"demo": "node tools/demo/record-demo.mjs"
|
|
37
|
+
"demo": "node tools/demo/record-demo.mjs",
|
|
38
|
+
"test:grant": "node tests/grant/executing-grant.mjs"
|
|
37
39
|
},
|
|
38
40
|
"keywords": [
|
|
39
41
|
"shapeup",
|
|
@@ -50,7 +50,7 @@ its phase; templates live in `assets/templates/`.
|
|
|
50
50
|
2 DDD bounded contexts, aggregates (new vs extended), value objects, domain events,
|
|
51
51
|
repository interfaces → domain-model.md [references/ddd-patterns.md]
|
|
52
52
|
2b CONTRACTS (standard lens) typed Request/Response/Error per repository; two-pass rule:
|
|
53
|
-
unresolvable at spec time → `⏳ TBD — verify in
|
|
53
|
+
unresolvable at spec time → `⏳ TBD — verify in the [UC-x] spike`, resolved
|
|
54
54
|
post-SPIKE with citation → contracts/ [references/contract-patterns.md]
|
|
55
55
|
3 UX per screen: state table (idle→loading→error→success), error cases with
|
|
56
56
|
message+action, ASCII flows → ux-behavior.md [references/ux-behavior-patterns.md]
|
|
@@ -64,10 +64,10 @@ its phase; templates live in `assets/templates/`.
|
|
|
64
64
|
6 TASKS atomic, ordered, executable → tasks/ (LOCAL root; the one uncommitted branch
|
|
65
65
|
of the tree — regenerable, machine-local) [references/task-generation.md]
|
|
66
66
|
7 DERIVE+LINT mechanical, not yours to grade:
|
|
67
|
-
node "${CLAUDE_PLUGIN_ROOT}/
|
|
67
|
+
node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" reduce board --slug <slug> --write
|
|
68
68
|
(unlocks = depends_on inverse; Σ hours; critical path; appetite arithmetic —
|
|
69
69
|
overflow is a fact you REPORT for the caller's HAMMER gate, never resolve)
|
|
70
|
-
node "${CLAUDE_PLUGIN_ROOT}/
|
|
70
|
+
node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" verify spec --slug <slug>
|
|
71
71
|
(structure, wikilinks, edge symmetry — fix reds, then re-run; you never
|
|
72
72
|
self-grade with a hand-walked checklist)
|
|
73
73
|
→ scope-summary.md + synthesis.md (traceability matrix, risk register,
|
|
@@ -165,7 +165,7 @@ status flips for built work (ingest's job), scope contracts (scope-architect's),
|
|
|
165
165
|
/ba-pitch-analyzer --order .shapeup/checkout-vnpay/orders/analyze.json
|
|
166
166
|
|
|
167
167
|
# Standalone — the preamble shim compiles the order (mode: standalone, pause_gates: true):
|
|
168
|
-
# node "${CLAUDE_PLUGIN_ROOT}/
|
|
168
|
+
# node "${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs" compile --operation analyze --slug <slug> \
|
|
169
169
|
# --worker ba-pitch-analyzer --payload '{"pitch": "docs/pitch.md", "lens": "standard"}'
|
|
170
170
|
/ba-pitch-analyzer docs/pitch.md # operation: analyze, lens judged
|
|
171
171
|
/ba-pitch-analyzer --lens standard docs/pitch.md # lens pinned
|
|
@@ -175,6 +175,6 @@ Standalone keeps exactly two flags: the pitch input and `--lens`. Every retired
|
|
|
175
175
|
caller context: `--tasks-only`/`--from-discovered` → a reconcile order, `--surface-only` → a
|
|
176
176
|
retrofit-surface order, `--coverage` → a coverage order, `--remap`/`--split` → a
|
|
177
177
|
scope-architect `map-scopes` order,
|
|
178
|
-
`--status` → read `spec
|
|
178
|
+
`--status` → read `harness verify spec`/`harness reduce board` output (zero LLM tokens),
|
|
179
179
|
`--auto`/`--skip-gate*` → `interaction.pause_gates`, `--upgrade` → an analyze order with the
|
|
180
180
|
standard lens over an existing lite tree (reconciliation pass: extend, never overwrite Steps).
|
|
@@ -64,7 +64,7 @@ audit_rules_version: "2.5"
|
|
|
64
64
|
|
|
65
65
|
## Audit Report
|
|
66
66
|
|
|
67
|
-
*Generated from spec
|
|
67
|
+
*Generated from harness verify spec output — do not edit manually.*
|
|
68
68
|
*skill_version: 2.1 | audit_rules_version: 2.1*
|
|
69
69
|
|
|
70
70
|
### Score Summary
|
|
@@ -14,9 +14,12 @@ dependent implementation tasks can proceed.*
|
|
|
14
14
|
|
|
15
15
|
## Detected Third-Party Dependencies
|
|
16
16
|
|
|
17
|
-
| # | Service | Capability Claimed | Status |
|
|
18
|
-
|
|
19
|
-
| API-01 | [ServiceName] | [What pitch claims this API can do] | ❓ UNVERIFIED |
|
|
17
|
+
| # | Service | Capability Claimed | Status | Spike |
|
|
18
|
+
|---|---------|-------------------|--------|-------|
|
|
19
|
+
| API-01 | [ServiceName] | [What pitch claims this API can do] | ❓ UNVERIFIED | ⏳ open |
|
|
20
|
+
|
|
21
|
+
<!-- The Spike column records STATUS (⏳ open / ✅ done / — none), never a task id.
|
|
22
|
+
This document is committed; the board is not, and its ids renumber per machine. -->
|
|
20
23
|
|
|
21
24
|
---
|
|
22
25
|
|
|
@@ -49,10 +52,12 @@ Specifically: [quote or paraphrase relevant pitch sentence].
|
|
|
49
52
|
| Partially supported | [Describe workaround] | +[N]h |
|
|
50
53
|
| Not supported | [Describe alternative or de-scope] | [TBD with PO] |
|
|
51
54
|
|
|
52
|
-
**
|
|
53
|
-
<!--
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
**Blocked Use Cases:**
|
|
56
|
+
<!-- What cannot be built until this investigation resolves. Name the committed UC, never
|
|
57
|
+
a task id: this doc is committed and task ids are machine-local. The tasks are found
|
|
58
|
+
by inverting use_case_refs over the LOCAL board. -->
|
|
59
|
+
- [[usecases/UC-Name]] — [reason blocked]
|
|
60
|
+
- [[usecases/UC-Name]] — [reason blocked]
|
|
56
61
|
|
|
57
62
|
**Decision Gate:**
|
|
58
63
|
- All questions answered + capability confirmed → update `contracts/[repo].contract.md`, remove ⏳ TBD fields, notify SPIKE complete
|
package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md
CHANGED
|
@@ -5,7 +5,9 @@ feature: "[feature-slug]"
|
|
|
5
5
|
repository: "[RepoName]"
|
|
6
6
|
service: "[ServiceName]"
|
|
7
7
|
feasibility_ref: "API-[NN]"
|
|
8
|
-
|
|
8
|
+
spike_status: open # open | done — STATUS, never a task id: this contract is
|
|
9
|
+
# committed and board ids renumber per machine. The spike is
|
|
10
|
+
# reached through feasibility_ref above.
|
|
9
11
|
status: speculative # → confirmed after SPIKE done
|
|
10
12
|
skill_version: "2.3"
|
|
11
13
|
---
|
|
@@ -14,13 +16,14 @@ skill_version: "2.3"
|
|
|
14
16
|
|
|
15
17
|
## Source Type: `third-party-api`
|
|
16
18
|
## Service: [ServiceName] ([REST API / SDK])
|
|
17
|
-
## Feasibility: ⏳ UNVERIFIED → see [[api-feasibility#API-NN]]
|
|
19
|
+
## Feasibility: ⏳ UNVERIFIED → see [[api-feasibility#API-NN]]
|
|
18
20
|
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
> ⚠️ **SPECULATIVE CONTRACT**
|
|
22
24
|
> Fields marked `⏳ TBD` are derived from pitch assumptions and community references.
|
|
23
|
-
> All `⏳ TBD` entries MUST be confirmed and replaced
|
|
25
|
+
> All `⏳ TBD` entries MUST be confirmed and replaced by the spike tracked at
|
|
26
|
+
> [[api-feasibility#API-NN]].
|
|
24
27
|
> Implementation tasks depending on this repository are **blocked** until no `⏳ TBD` remains.
|
|
25
28
|
|
|
26
29
|
---
|
|
@@ -13,7 +13,8 @@ status: draft
|
|
|
13
13
|
# Migration Plan: FEATURE TITLE
|
|
14
14
|
|
|
15
15
|
> Generated only when feature modifies existing production schema.
|
|
16
|
-
> Each migration
|
|
16
|
+
> Each migration step anchors to a committed use case; the tasks that carry it are found
|
|
17
|
+
> by inverting `use_case_refs` over the LOCAL board, never linked from here.
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
@@ -31,7 +32,7 @@ status: draft
|
|
|
31
32
|
|
|
32
33
|
## Migration Tasks
|
|
33
34
|
|
|
34
|
-
###
|
|
35
|
+
### STEP-1 — [description, e.g. Add nullable columns]
|
|
35
36
|
|
|
36
37
|
```sql
|
|
37
38
|
-- UP
|
|
@@ -46,7 +47,7 @@ Safe to run: during deploy / after deploy / off-hours only
|
|
|
46
47
|
|
|
47
48
|
---
|
|
48
49
|
|
|
49
|
-
###
|
|
50
|
+
### STEP-2 — [description, e.g. Backfill existing rows]
|
|
50
51
|
|
|
51
52
|
```sql
|
|
52
53
|
-- UP (run in batches of 1000 to avoid lock escalation)
|
|
@@ -66,7 +67,7 @@ Estimated rows: ~N | Estimated time: ~Ns | Lock: none
|
|
|
66
67
|
|
|
67
68
|
---
|
|
68
69
|
|
|
69
|
-
###
|
|
70
|
+
### STEP-3 — [description, e.g. Enforce NOT NULL constraint]
|
|
70
71
|
|
|
71
72
|
```sql
|
|
72
73
|
-- UP — only run after M02 verified
|
|
@@ -21,19 +21,29 @@ status: draft
|
|
|
21
21
|
|
|
22
22
|
| From Team | To Team | Artifact | Ready When | Blocker Risk |
|
|
23
23
|
|-----------|---------|----------|-----------|-------------|
|
|
24
|
-
| [Team A] (API) | [Team B] (Mobile) | [[contracts/[repo].contract.md]] |
|
|
25
|
-
| [Team B] (API) | [Team A] (API) | `EventName` schema |
|
|
26
|
-
| [Team B] (Mobile) | QA | E2E test cases |
|
|
24
|
+
| [Team A] (API) | [Team B] (Mobile) | [[contracts/[repo].contract.md]] | [[usecases/UC-Name]] green | 🔴 blocks Mobile [[usecases/UC-Name]] |
|
|
25
|
+
| [Team B] (API) | [Team A] (API) | `EventName` schema | [[usecases/UC-Name]] green | 🟡 blocks integration test |
|
|
26
|
+
| [Team B] (Mobile) | QA | E2E test cases | [[usecases/UC-Name]] green | 🟡 blocks QA sprint entry |
|
|
27
|
+
|
|
28
|
+
<!-- Readiness is stated against a committed UC (or a scope_id), never a task id: this
|
|
29
|
+
register is committed and shared across teams, and board ids are per-machine. -->
|
|
27
30
|
|
|
28
31
|
---
|
|
29
32
|
|
|
30
33
|
## Blocking Dependencies
|
|
31
34
|
|
|
35
|
+
<!--
|
|
36
|
+
Key every row on a USE CASE or a scope_id, never a task id. This document is committed and
|
|
37
|
+
crosses a team boundary; board ids live in the gitignored tier and each team's board numbers
|
|
38
|
+
its own, so one board id names a different piece of work on every machine that reads this.
|
|
39
|
+
spec-lint reds a board id anywhere in the committed tree (TIER-DIRECTION).
|
|
40
|
+
-->
|
|
41
|
+
|
|
32
42
|
```
|
|
33
|
-
[Team B] CANNOT start
|
|
34
|
-
until [Team A] completes
|
|
43
|
+
[Team B] CANNOT start [UC-x] ([description])
|
|
44
|
+
until [Team A] completes [UC-y] ([description])
|
|
35
45
|
Mitigation: use contract stub from [[contracts/[repo].contract.md]]
|
|
36
|
-
|
|
46
|
+
Ready now: [UC-z] (unblocked — no dependency)
|
|
37
47
|
|
|
38
48
|
[Team C] CANNOT start integration tests
|
|
39
49
|
until [Team A] EventName schema is stable
|
|
@@ -46,12 +56,12 @@ status: draft
|
|
|
46
56
|
|
|
47
57
|
```
|
|
48
58
|
Wave 1 — No cross-team dependencies (start immediately):
|
|
49
|
-
[Team A]:
|
|
50
|
-
[Team B]:
|
|
59
|
+
[Team A]: [UC-a], [UC-b], [UC-c]
|
|
60
|
+
[Team B]: [UC-d], [UC-e] (using contract stub)
|
|
51
61
|
|
|
52
|
-
Wave 2 — After Team A
|
|
53
|
-
[Team B]:
|
|
54
|
-
[Team C]:
|
|
62
|
+
Wave 2 — After Team A [UC-c] done:
|
|
63
|
+
[Team B]: [UC-f] (replace stub with real contract)
|
|
64
|
+
[Team C]: integration tests (real contract available)
|
|
55
65
|
|
|
56
66
|
Wave 3 — After Wave 2 complete:
|
|
57
67
|
QA: E2E test suite
|
|
@@ -64,7 +74,7 @@ Wave 3 — After Wave 2 complete:
|
|
|
64
74
|
| Trigger | Owner | Notify | Channel |
|
|
65
75
|
|---------|-------|--------|---------|
|
|
66
76
|
| Contract changes after stub distributed | [Team A] | [Team B], QA | [channel] |
|
|
67
|
-
|
|
|
77
|
+
| A wave-1 use case delayed > 1 day | [Team A] | [Team B] | [channel] |
|
|
68
78
|
| Schema breaking change detected | Any | All teams | [channel] |
|
|
69
79
|
|
|
70
80
|
---
|
|
@@ -72,7 +82,7 @@ Wave 3 — After Wave 2 complete:
|
|
|
72
82
|
## Definition of Done (Cross-Context)
|
|
73
83
|
|
|
74
84
|
Feature is complete when ALL of the following are true:
|
|
75
|
-
- [ ]
|
|
85
|
+
- [ ] Every migration STEP run and verified in staging
|
|
76
86
|
- [ ] All contracts have no remaining ⏳ TBD fields
|
|
77
87
|
- [ ] Event choreography happy path verified end-to-end
|
|
78
88
|
- [ ] All dead-letter scenarios have runbooks
|