@slowcook-ai/cli 0.19.6 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +22 -2
  2. package/dist/cli.js +30 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/brand/index.d.ts.map +1 -1
  5. package/dist/commands/brand/index.js +6 -0
  6. package/dist/commands/brand/index.js.map +1 -1
  7. package/dist/commands/brand/logo-cmd.d.ts +2 -0
  8. package/dist/commands/brand/logo-cmd.d.ts.map +1 -0
  9. package/dist/commands/brand/logo-cmd.js +90 -0
  10. package/dist/commands/brand/logo-cmd.js.map +1 -0
  11. package/dist/commands/brand/logo.d.ts +23 -0
  12. package/dist/commands/brand/logo.d.ts.map +1 -0
  13. package/dist/commands/brand/logo.js +60 -0
  14. package/dist/commands/brand/logo.js.map +1 -0
  15. package/dist/commands/brew/fidelity-loop.d.ts +71 -0
  16. package/dist/commands/brew/fidelity-loop.d.ts.map +1 -0
  17. package/dist/commands/brew/fidelity-loop.js +108 -0
  18. package/dist/commands/brew/fidelity-loop.js.map +1 -0
  19. package/dist/commands/brew/fidelity-phase.d.ts +49 -0
  20. package/dist/commands/brew/fidelity-phase.d.ts.map +1 -0
  21. package/dist/commands/brew/fidelity-phase.js +75 -0
  22. package/dist/commands/brew/fidelity-phase.js.map +1 -0
  23. package/dist/commands/eye/index.d.ts +2 -0
  24. package/dist/commands/eye/index.d.ts.map +1 -0
  25. package/dist/commands/eye/index.js +83 -0
  26. package/dist/commands/eye/index.js.map +1 -0
  27. package/dist/commands/eye/plan.d.ts +69 -0
  28. package/dist/commands/eye/plan.d.ts.map +1 -0
  29. package/dist/commands/eye/plan.js +87 -0
  30. package/dist/commands/eye/plan.js.map +1 -0
  31. package/dist/commands/eye/run.d.ts +42 -0
  32. package/dist/commands/eye/run.d.ts.map +1 -0
  33. package/dist/commands/eye/run.js +116 -0
  34. package/dist/commands/eye/run.js.map +1 -0
  35. package/dist/commands/eye/spec-modes.d.ts +5 -0
  36. package/dist/commands/eye/spec-modes.d.ts.map +1 -0
  37. package/dist/commands/eye/spec-modes.js +36 -0
  38. package/dist/commands/eye/spec-modes.js.map +1 -0
  39. package/dist/commands/gate/github.d.ts +27 -0
  40. package/dist/commands/gate/github.d.ts.map +1 -0
  41. package/dist/commands/gate/github.js +46 -0
  42. package/dist/commands/gate/github.js.map +1 -0
  43. package/dist/commands/gate/index.d.ts +2 -0
  44. package/dist/commands/gate/index.d.ts.map +1 -0
  45. package/dist/commands/gate/index.js +68 -0
  46. package/dist/commands/gate/index.js.map +1 -0
  47. package/dist/commands/gate/model.d.ts +55 -0
  48. package/dist/commands/gate/model.d.ts.map +1 -0
  49. package/dist/commands/gate/model.js +64 -0
  50. package/dist/commands/gate/model.js.map +1 -0
  51. package/dist/commands/gate/reviewers.d.ts +24 -0
  52. package/dist/commands/gate/reviewers.d.ts.map +1 -0
  53. package/dist/commands/gate/reviewers.js +69 -0
  54. package/dist/commands/gate/reviewers.js.map +1 -0
  55. package/dist/commands/greenfield/index.d.ts +2 -0
  56. package/dist/commands/greenfield/index.d.ts.map +1 -0
  57. package/dist/commands/greenfield/index.js +80 -0
  58. package/dist/commands/greenfield/index.js.map +1 -0
  59. package/dist/commands/greenfield/status.d.ts +38 -0
  60. package/dist/commands/greenfield/status.d.ts.map +1 -0
  61. package/dist/commands/greenfield/status.js +52 -0
  62. package/dist/commands/greenfield/status.js.map +1 -0
  63. package/dist/commands/menu/assemble.d.ts +31 -0
  64. package/dist/commands/menu/assemble.d.ts.map +1 -0
  65. package/dist/commands/menu/assemble.js +37 -0
  66. package/dist/commands/menu/assemble.js.map +1 -0
  67. package/dist/commands/menu/index.d.ts +5 -0
  68. package/dist/commands/menu/index.d.ts.map +1 -0
  69. package/dist/commands/menu/index.js +111 -0
  70. package/dist/commands/menu/index.js.map +1 -0
  71. package/dist/commands/menu/prd.d.ts +33 -0
  72. package/dist/commands/menu/prd.d.ts.map +1 -0
  73. package/dist/commands/menu/prd.js +68 -0
  74. package/dist/commands/menu/prd.js.map +1 -0
  75. package/dist/commands/plate/index.d.ts.map +1 -1
  76. package/dist/commands/plate/index.js +5 -1
  77. package/dist/commands/plate/index.js.map +1 -1
  78. package/dist/commands/plate/route-hint.d.ts +18 -0
  79. package/dist/commands/plate/route-hint.d.ts.map +1 -0
  80. package/dist/commands/plate/route-hint.js +21 -0
  81. package/dist/commands/plate/route-hint.js.map +1 -0
  82. package/dist/commands/recon/shape-preserve.d.ts +38 -0
  83. package/dist/commands/recon/shape-preserve.d.ts.map +1 -1
  84. package/dist/commands/recon/shape-preserve.js +112 -1
  85. package/dist/commands/recon/shape-preserve.js.map +1 -1
  86. package/dist/commands/refine/spec-yaml.d.ts +26 -0
  87. package/dist/commands/refine/spec-yaml.d.ts.map +1 -1
  88. package/dist/commands/refine/spec-yaml.js +29 -0
  89. package/dist/commands/refine/spec-yaml.js.map +1 -1
  90. package/dist/commands/run-mock/index.d.ts.map +1 -1
  91. package/dist/commands/run-mock/index.js +69 -16
  92. package/dist/commands/run-mock/index.js.map +1 -1
  93. package/dist/commands/run-mock/reviewer-auth-server.d.ts +40 -0
  94. package/dist/commands/run-mock/reviewer-auth-server.d.ts.map +1 -0
  95. package/dist/commands/run-mock/reviewer-auth-server.js +124 -0
  96. package/dist/commands/run-mock/reviewer-auth-server.js.map +1 -0
  97. package/dist/commands/serve/config.d.ts +28 -9
  98. package/dist/commands/serve/config.d.ts.map +1 -1
  99. package/dist/commands/serve/config.js +43 -1
  100. package/dist/commands/serve/config.js.map +1 -1
  101. package/dist/commands/serve/dev.d.ts +14 -19
  102. package/dist/commands/serve/dev.d.ts.map +1 -1
  103. package/dist/commands/serve/dev.js +46 -50
  104. package/dist/commands/serve/dev.js.map +1 -1
  105. package/dist/commands/serve/index.d.ts.map +1 -1
  106. package/dist/commands/serve/index.js +30 -22
  107. package/dist/commands/serve/index.js.map +1 -1
  108. package/dist/commands/serve/mock.d.ts +5 -20
  109. package/dist/commands/serve/mock.d.ts.map +1 -1
  110. package/dist/commands/serve/mock.js +44 -65
  111. package/dist/commands/serve/mock.js.map +1 -1
  112. package/dist/commands/serve/runner.d.ts +52 -0
  113. package/dist/commands/serve/runner.d.ts.map +1 -0
  114. package/dist/commands/serve/runner.js +53 -0
  115. package/dist/commands/serve/runner.js.map +1 -0
  116. package/dist/commands/serve/staging.d.ts +8 -19
  117. package/dist/commands/serve/staging.d.ts.map +1 -1
  118. package/dist/commands/serve/staging.js +53 -91
  119. package/dist/commands/serve/staging.js.map +1 -1
  120. package/dist/commands/trace/check.d.ts +67 -0
  121. package/dist/commands/trace/check.d.ts.map +1 -0
  122. package/dist/commands/trace/check.js +82 -0
  123. package/dist/commands/trace/check.js.map +1 -0
  124. package/dist/commands/trace/index.d.ts +2 -0
  125. package/dist/commands/trace/index.d.ts.map +1 -0
  126. package/dist/commands/trace/index.js +86 -0
  127. package/dist/commands/trace/index.js.map +1 -0
  128. package/dist/commands/upsert-agent-docs.d.ts.map +1 -1
  129. package/dist/commands/upsert-agent-docs.js +12 -0
  130. package/dist/commands/upsert-agent-docs.js.map +1 -1
  131. package/dist/commands.manifest.d.ts.map +1 -1
  132. package/dist/commands.manifest.js +32 -2
  133. package/dist/commands.manifest.js.map +1 -1
  134. package/dist/lib/mock-shape.d.ts +6 -0
  135. package/dist/lib/mock-shape.d.ts.map +1 -1
  136. package/dist/lib/mock-shape.js +26 -0
  137. package/dist/lib/mock-shape.js.map +1 -1
  138. package/package.json +8 -6
@@ -0,0 +1,27 @@
1
+ /**
2
+ * design #9 — map GitHub PR reviews to the Approval shape `isGateSatisfied`
3
+ * grades. Pure + unit-tested; the live `gh api` fetch lives in ./index.ts.
4
+ *
5
+ * The identity classification is the load-bearing bit: a review authored by a
6
+ * Bot account (GitHub `user.type === "Bot"`, or a login ending in `[bot]`, or a
7
+ * known slowcook bot handle) is marked `identityType: "bot"` so it can never
8
+ * satisfy a human-review gate — the automation cannot self-approve.
9
+ */
10
+ import type { Approval } from "./model.js";
11
+ /** Subset of the GitHub PR-review payload we consume. */
12
+ export interface GhReview {
13
+ user?: {
14
+ login?: string;
15
+ type?: string;
16
+ } | null;
17
+ state?: string;
18
+ }
19
+ /** Logins always treated as bots regardless of the GitHub `type` field. */
20
+ export declare const BOT_LOGINS: ReadonlyArray<string>;
21
+ /**
22
+ * Convert raw PR reviews into Approvals. Dismissed/pending reviews are dropped.
23
+ * Only the latest review per author is kept (GitHub returns reviews
24
+ * chronologically; a later review supersedes an earlier one from the same user).
25
+ */
26
+ export declare function mapReviewsToApprovals(reviews: GhReview[]): Approval[];
27
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,yDAAyD;AACzD,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,eAAO,MAAM,UAAU,EAAE,aAAa,CAAC,MAAM,CAAsB,CAAC;AAsBpE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAcrE"}
@@ -0,0 +1,46 @@
1
+ /** Logins always treated as bots regardless of the GitHub `type` field. */
2
+ export const BOT_LOGINS = ["github-actions"];
3
+ function isBot(login, type) {
4
+ if (type === "Bot")
5
+ return true;
6
+ if (login.endsWith("[bot]"))
7
+ return true;
8
+ if (BOT_LOGINS.includes(login))
9
+ return true;
10
+ return false;
11
+ }
12
+ function mapState(state) {
13
+ switch (state) {
14
+ case "APPROVED":
15
+ return "approved";
16
+ case "CHANGES_REQUESTED":
17
+ return "rejected";
18
+ case "COMMENTED":
19
+ return "commented";
20
+ default:
21
+ return null; // DISMISSED / PENDING / unknown → not a signal
22
+ }
23
+ }
24
+ /**
25
+ * Convert raw PR reviews into Approvals. Dismissed/pending reviews are dropped.
26
+ * Only the latest review per author is kept (GitHub returns reviews
27
+ * chronologically; a later review supersedes an earlier one from the same user).
28
+ */
29
+ export function mapReviewsToApprovals(reviews) {
30
+ const latestByAuthor = new Map();
31
+ for (const r of reviews) {
32
+ const login = (r.user?.login ?? "").toLowerCase();
33
+ if (!login)
34
+ continue;
35
+ const state = mapState(r.state);
36
+ if (!state)
37
+ continue;
38
+ latestByAuthor.set(login, {
39
+ byHandle: login,
40
+ state,
41
+ identityType: isBot(login, r.user?.type ?? undefined) ? "bot" : "human",
42
+ });
43
+ }
44
+ return [...latestByAuthor.values()];
45
+ }
46
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../../src/commands/gate/github.ts"],"names":[],"mappings":"AAiBA,2EAA2E;AAC3E,MAAM,CAAC,MAAM,UAAU,GAA0B,CAAC,gBAAgB,CAAC,CAAC;AAEpE,SAAS,KAAK,CAAC,KAAa,EAAE,IAAwB;IACpD,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB;IACzC,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,mBAAmB;YACtB,OAAO,UAAU,CAAC;QACpB,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB;YACE,OAAO,IAAI,CAAC,CAAC,+CAA+C;IAChE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAmB;IACvD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAoB,CAAC;IACnD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAClD,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE;YACxB,QAAQ,EAAE,KAAK;YACf,KAAK;YACL,YAAY,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO;SACxE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function gate(args: string[], _version: string): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"AAmCA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC1E"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * design #9 — `slowcook gate check`. The dispatch-time HITL halt: refuse to let
3
+ * a stage proceed until a human in the required role has approved on the PR.
4
+ *
5
+ * slowcook gate check --stage <refine|plate|brew> --pr <n> [--repo owner/name]
6
+ *
7
+ * Exit 0 = gate satisfied (advance). Exit 1 = blocked (a human in the required
8
+ * role(s) must approve, or a rejection must be resolved). Because approvals are
9
+ * classified by identity (./github.ts) and only human reviewers in the role's
10
+ * handle-list count (./model.ts), the automation cannot satisfy its own gate.
11
+ */
12
+ import { execFileSync } from "node:child_process";
13
+ import { loadReviewers } from "./reviewers.js";
14
+ import { DEFAULT_GATES, isGateSatisfied } from "./model.js";
15
+ import { mapReviewsToApprovals } from "./github.js";
16
+ function val(args, flag) {
17
+ const i = args.indexOf(flag);
18
+ return i >= 0 ? args[i + 1] : undefined;
19
+ }
20
+ function fetchReviews(repo, pr) {
21
+ const out = execFileSync("gh", ["api", `repos/${repo}/pulls/${pr}/reviews`, "--paginate"], {
22
+ encoding: "utf8",
23
+ });
24
+ return JSON.parse(out);
25
+ }
26
+ function detectRepo() {
27
+ const out = execFileSync("gh", ["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"], {
28
+ encoding: "utf8",
29
+ });
30
+ return out.trim();
31
+ }
32
+ export async function gate(args, _version) {
33
+ const sub = args[0];
34
+ if (sub !== "check") {
35
+ console.error("usage: slowcook gate check --stage <stage> --pr <n> [--repo owner/name]");
36
+ process.exit(64);
37
+ }
38
+ const rest = args.slice(1);
39
+ const stage = val(rest, "--stage");
40
+ const pr = val(rest, "--pr");
41
+ if (!stage || !pr) {
42
+ console.error("gate check: --stage and --pr are required");
43
+ process.exit(64);
44
+ }
45
+ const gateDef = DEFAULT_GATES.find((g) => g.stage === stage);
46
+ if (!gateDef) {
47
+ console.error(`gate check: no gate defined for stage '${stage}' (have: ${DEFAULT_GATES.map((g) => g.stage).join(", ")})`);
48
+ process.exit(64);
49
+ }
50
+ const repo = val(rest, "--repo") ?? detectRepo();
51
+ const reviewers = loadReviewers(process.cwd());
52
+ const approvals = mapReviewsToApprovals(fetchReviews(repo, pr));
53
+ const verdict = isGateSatisfied(gateDef, reviewers, approvals);
54
+ if (verdict.satisfied) {
55
+ console.log(`gate '${stage}' ✓ satisfied — ${verdict.reason}`);
56
+ return;
57
+ }
58
+ // Blocked. Name exactly who must act.
59
+ const need = verdict.rejected
60
+ ? verdict.reason
61
+ : verdict.missingRoles
62
+ .map((r) => `${r} (${reviewers.roles[r]?.join(", ") || "no reviewers configured in .brewing/reviewers.yaml"})`)
63
+ .join("; ");
64
+ console.error(`gate '${stage}' ✗ blocked-on-review — ${verdict.reason}`);
65
+ console.error(` needs approval from: ${need}`);
66
+ process.exit(1);
67
+ }
68
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * design #9 — HITL role gates: the gate-integrity core.
3
+ *
4
+ * Decides whether a pipeline stage may proceed past a human-review gate.
5
+ * The load-bearing security property: an approval only counts if it
6
+ * comes from a HUMAN identity that is in the configured handle-list for
7
+ * the required role. A bot/agent approval, or an approval from someone
8
+ * not assigned that role, NEVER satisfies a gate. This is what makes the
9
+ * halt unforgeable by the automation driving the pipeline.
10
+ */
11
+ import { type ReviewersConfig } from "./reviewers.js";
12
+ export type Role = "pm" | "designer" | "qa";
13
+ export type Stage = "refine" | "plate" | "brew" | string;
14
+ export interface Gate {
15
+ stage: Stage;
16
+ requiredRoles: Role[];
17
+ approvalSignal: "review" | "comment";
18
+ onRejectTarget: Stage;
19
+ }
20
+ export interface Approval {
21
+ /** GitHub handle of the approver. */
22
+ byHandle: string;
23
+ state: "approved" | "rejected" | "commented";
24
+ /** bot = slowcook-*[bot] / the driving agent; human = a real reviewer. */
25
+ identityType: "human" | "bot";
26
+ }
27
+ export interface GateVerdict {
28
+ satisfied: boolean;
29
+ /** required roles lacking a valid human approval. */
30
+ missingRoles: Role[];
31
+ /** a valid human reviewer for a required role rejected. */
32
+ rejected: boolean;
33
+ /** human-readable summary. */
34
+ reason: string;
35
+ }
36
+ /**
37
+ * The standard pipeline gates. refine is signed off by a PM, plate by a
38
+ * designer, and brew needs BOTH qa and designer before code ships.
39
+ */
40
+ export declare const DEFAULT_GATES: Gate[];
41
+ /**
42
+ * Evaluate a gate against the reviewer roster and the observed approvals.
43
+ *
44
+ * - A valid approval for role R = human + state 'approved' + handle in
45
+ * the role's configured list.
46
+ * - A valid rejection for role R = same, but state 'rejected'.
47
+ * - `rejected` is true if ANY required role has a valid rejection; a
48
+ * rejected gate is never satisfied (and routes back to onRejectTarget,
49
+ * handled by the caller).
50
+ * - `missingRoles` lists required roles with no valid approval.
51
+ * - `satisfied` requires every required role to have a valid approval
52
+ * AND no valid rejection.
53
+ */
54
+ export declare function isGateSatisfied(gate: Gate, reviewers: ReviewersConfig, approvals: Approval[]): GateVerdict;
55
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEnE,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAAC;AAC5C,MAAM,MAAM,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,KAAK,CAAC;IACb,aAAa,EAAE,IAAI,EAAE,CAAC;IACtB,cAAc,EAAE,QAAQ,GAAG,SAAS,CAAC;IACrC,cAAc,EAAE,KAAK,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,WAAW,CAAC;IAC7C,0EAA0E;IAC1E,YAAY,EAAE,OAAO,GAAG,KAAK,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,qDAAqD;IACrD,YAAY,EAAE,IAAI,EAAE,CAAC;IACrB,2DAA2D;IAC3D,QAAQ,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,IAAI,EAI/B,CAAC;AAwBF;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,eAAe,EAC1B,SAAS,EAAE,QAAQ,EAAE,GACpB,WAAW,CAoBb"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * design #9 — HITL role gates: the gate-integrity core.
3
+ *
4
+ * Decides whether a pipeline stage may proceed past a human-review gate.
5
+ * The load-bearing security property: an approval only counts if it
6
+ * comes from a HUMAN identity that is in the configured handle-list for
7
+ * the required role. A bot/agent approval, or an approval from someone
8
+ * not assigned that role, NEVER satisfies a gate. This is what makes the
9
+ * halt unforgeable by the automation driving the pipeline.
10
+ */
11
+ import { resolveRole } from "./reviewers.js";
12
+ /**
13
+ * The standard pipeline gates. refine is signed off by a PM, plate by a
14
+ * designer, and brew needs BOTH qa and designer before code ships.
15
+ */
16
+ export const DEFAULT_GATES = [
17
+ { stage: "refine", requiredRoles: ["pm"], approvalSignal: "review", onRejectTarget: "refine" },
18
+ { stage: "plate", requiredRoles: ["designer"], approvalSignal: "review", onRejectTarget: "plate" },
19
+ { stage: "brew", requiredRoles: ["qa", "designer"], approvalSignal: "review", onRejectTarget: "brew" },
20
+ ];
21
+ /**
22
+ * True when `approvals` contains an approval in `state` for role `role`
23
+ * that is BOTH human-authored AND from a handle configured for that role
24
+ * in `reviewers`. Handle matching is case-insensitive (both sides
25
+ * lowercased). This is the single chokepoint enforcing the integrity
26
+ * property — a bot identity or an unconfigured handle can never pass.
27
+ */
28
+ function hasValidSignal(reviewers, approvals, role, state) {
29
+ const allowed = new Set(resolveRole(reviewers, role)); // already lowercased on load
30
+ return approvals.some((a) => a.identityType === "human" &&
31
+ a.state === state &&
32
+ allowed.has(a.byHandle.toLowerCase()));
33
+ }
34
+ /**
35
+ * Evaluate a gate against the reviewer roster and the observed approvals.
36
+ *
37
+ * - A valid approval for role R = human + state 'approved' + handle in
38
+ * the role's configured list.
39
+ * - A valid rejection for role R = same, but state 'rejected'.
40
+ * - `rejected` is true if ANY required role has a valid rejection; a
41
+ * rejected gate is never satisfied (and routes back to onRejectTarget,
42
+ * handled by the caller).
43
+ * - `missingRoles` lists required roles with no valid approval.
44
+ * - `satisfied` requires every required role to have a valid approval
45
+ * AND no valid rejection.
46
+ */
47
+ export function isGateSatisfied(gate, reviewers, approvals) {
48
+ const rejectingRoles = gate.requiredRoles.filter((role) => hasValidSignal(reviewers, approvals, role, "rejected"));
49
+ const missingRoles = gate.requiredRoles.filter((role) => !hasValidSignal(reviewers, approvals, role, "approved"));
50
+ const rejected = rejectingRoles.length > 0;
51
+ const satisfied = !rejected && missingRoles.length === 0;
52
+ let reason;
53
+ if (rejected) {
54
+ reason = `rejected by ${rejectingRoles.join(", ")}`;
55
+ }
56
+ else if (missingRoles.length > 0) {
57
+ reason = `blocked: missing ${missingRoles.join(", ")} approval`;
58
+ }
59
+ else {
60
+ reason = "satisfied";
61
+ }
62
+ return { satisfied, missingRoles, rejected, reason };
63
+ }
64
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/commands/gate/model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAwB,MAAM,gBAAgB,CAAC;AA8BnE;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAW;IACnC,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE;IAC9F,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,UAAU,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE;IAClG,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE;CACvG,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,cAAc,CACrB,SAA0B,EAC1B,SAAqB,EACrB,IAAU,EACV,KAA8B;IAE9B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,6BAA6B;IACpF,OAAO,SAAS,CAAC,IAAI,CACnB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,YAAY,KAAK,OAAO;QAC1B,CAAC,CAAC,KAAK,KAAK,KAAK;QACjB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CACxC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAU,EACV,SAA0B,EAC1B,SAAqB;IAErB,MAAM,cAAc,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACxD,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,CAAC,CACvD,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAC5C,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,CAAC,CAClE,CAAC;IACF,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,CAAC,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC;IAEzD,IAAI,MAAc,CAAC;IACnB,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,GAAG,eAAe,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACtD,CAAC;SAAM,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,MAAM,GAAG,oBAAoB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,WAAW,CAAC;IACvB,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ declare const ReviewersConfigSchema: z.ZodObject<{
3
+ schema_version: z.ZodLiteral<1>;
4
+ roles: z.ZodPipe<z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>, z.ZodTransform<Record<string, string[]>, Record<string, string[]>>>;
5
+ }, z.core.$strip>;
6
+ export type ReviewersConfig = z.infer<typeof ReviewersConfigSchema>;
7
+ declare const EMPTY_DEFAULT: ReviewersConfig;
8
+ /**
9
+ * Load `.brewing/reviewers.yaml`. Returns an empty roster
10
+ * (`{ schema_version: 1, roles: {} }`) when the file is absent — a repo
11
+ * with no roster has no configured reviewers, so every role gate is
12
+ * unsatisfiable until one is authored (fail-closed). Throws on parse
13
+ * error / schema violation so a mis-authored roster surfaces loudly
14
+ * rather than silently granting or denying approvals.
15
+ */
16
+ export declare function loadReviewers(repoRoot: string): ReviewersConfig;
17
+ /**
18
+ * Returns the configured handles for a role (already lowercased), or an
19
+ * empty array when the role is unset. An empty array means the role can
20
+ * never be satisfied — fail-closed by design.
21
+ */
22
+ export declare function resolveRole(cfg: ReviewersConfig, role: string): string[];
23
+ export { EMPTY_DEFAULT };
24
+ //# sourceMappingURL=reviewers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewers.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/reviewers.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,qBAAqB;;;iBAazB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,QAAA,MAAM,aAAa,EAAE,eAGpB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAa/D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAExE;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * design #9 — HITL role gates: reviewer roster.
3
+ *
4
+ * Reads `.brewing/reviewers.yaml` to map review roles (pm, designer, qa,
5
+ * …) to the GitHub handles authorised to satisfy that role's gate. This
6
+ * roster is the trust anchor for the gate-integrity core: an approval
7
+ * only counts if its author is a configured handle for the required
8
+ * role (see `./model.js`).
9
+ *
10
+ * Handles are lowercased on load so downstream matching against the
11
+ * (also lowercased) approver handle is case-insensitive — GitHub login
12
+ * comparison is case-insensitive and a gate must not be bypassable by a
13
+ * casing mismatch.
14
+ *
15
+ * Single source of truth: nothing else should hard-code the roster
16
+ * location or shape.
17
+ */
18
+ import { existsSync, readFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import YAML from "yaml";
21
+ import { z } from "zod";
22
+ const ReviewersConfigSchema = z.object({
23
+ schema_version: z.literal(1),
24
+ // role -> list of GitHub handles. Lowercased on load (see transform).
25
+ roles: z
26
+ .record(z.string(), z.array(z.string()))
27
+ .default({})
28
+ .transform((roles) => {
29
+ const out = {};
30
+ for (const [role, handles] of Object.entries(roles)) {
31
+ out[role] = handles.map((h) => h.toLowerCase());
32
+ }
33
+ return out;
34
+ }),
35
+ });
36
+ const EMPTY_DEFAULT = {
37
+ schema_version: 1,
38
+ roles: {},
39
+ };
40
+ /**
41
+ * Load `.brewing/reviewers.yaml`. Returns an empty roster
42
+ * (`{ schema_version: 1, roles: {} }`) when the file is absent — a repo
43
+ * with no roster has no configured reviewers, so every role gate is
44
+ * unsatisfiable until one is authored (fail-closed). Throws on parse
45
+ * error / schema violation so a mis-authored roster surfaces loudly
46
+ * rather than silently granting or denying approvals.
47
+ */
48
+ export function loadReviewers(repoRoot) {
49
+ const p = join(repoRoot, ".brewing", "reviewers.yaml");
50
+ if (!existsSync(p)) {
51
+ return { schema_version: 1, roles: {} };
52
+ }
53
+ const raw = YAML.parse(readFileSync(p, "utf8"));
54
+ const parsed = ReviewersConfigSchema.safeParse(raw);
55
+ if (!parsed.success) {
56
+ throw new Error(`Invalid .brewing/reviewers.yaml: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
57
+ }
58
+ return parsed.data;
59
+ }
60
+ /**
61
+ * Returns the configured handles for a role (already lowercased), or an
62
+ * empty array when the role is unset. An empty array means the role can
63
+ * never be satisfied — fail-closed by design.
64
+ */
65
+ export function resolveRole(cfg, role) {
66
+ return cfg.roles[role] ?? [];
67
+ }
68
+ export { EMPTY_DEFAULT };
69
+ //# sourceMappingURL=reviewers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reviewers.js","sourceRoot":"","sources":["../../../src/commands/gate/reviewers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5B,sEAAsE;IACtE,KAAK,EAAE,CAAC;SACL,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;SACvC,OAAO,CAAC,EAAE,CAAC;SACX,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;QACnB,MAAM,GAAG,GAA6B,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACpD,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;CACL,CAAC,CAAC;AAIH,MAAM,aAAa,GAAoB;IACrC,cAAc,EAAE,CAAC;IACjB,KAAK,EAAE,EAAE;CACV,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,cAAc,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC1C,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,oCAAoC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3F,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,GAAoB,EAAE,IAAY;IAC5D,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AAC/B,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function greenfield(argv: string[], _cliVersion: string): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/greenfield/index.ts"],"names":[],"mappings":"AAoCA,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDnF"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * GUCDI — `slowcook greenfield status`. The scope-completeness dashboard: where
3
+ * the project is in PRD → stories → brand → LCR → trace, and the single next
4
+ * action. Makes the step-by-step greenfield flow navigable (the individual
5
+ * agents — menu/brand/vibe/eye/trace — already exist).
6
+ *
7
+ * slowcook greenfield status [--prd <path>] [--cwd <dir>]
8
+ *
9
+ * Pure planner in ./status.ts; this is the IO shell.
10
+ */
11
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
12
+ import { join, relative, resolve } from "node:path";
13
+ import { listActiveSpecs } from "../refine/spec-yaml.js";
14
+ import { loadMockShapeConfig } from "../../lib/mock-shape.js";
15
+ import { parsePrdInitiatives } from "../menu/prd.js";
16
+ import { checkTrace, parseLcrProvenance } from "../trace/check.js";
17
+ import { computeGreenfieldStatus } from "./status.js";
18
+ function val(args, flag) {
19
+ const i = args.indexOf(flag);
20
+ return i >= 0 ? args[i + 1] : undefined;
21
+ }
22
+ function walkTsx(dir, exclude) {
23
+ if (!existsSync(dir))
24
+ return [];
25
+ const out = [];
26
+ for (const name of readdirSync(dir)) {
27
+ const p = join(dir, name);
28
+ if (exclude.has(p) || name === "node_modules")
29
+ continue;
30
+ const st = statSync(p);
31
+ if (st.isDirectory())
32
+ out.push(...walkTsx(p, exclude));
33
+ else if (name.endsWith(".tsx"))
34
+ out.push(p);
35
+ }
36
+ return out;
37
+ }
38
+ export async function greenfield(argv, _cliVersion) {
39
+ if (argv[0] !== "status") {
40
+ console.error("usage: slowcook greenfield status [--prd <path>] [--cwd <dir>]");
41
+ process.exit(64);
42
+ }
43
+ const rest = argv.slice(1);
44
+ const cwd = resolve(val(rest, "--cwd") ?? ".");
45
+ const specs = listActiveSpecs(cwd);
46
+ const prdRel = val(rest, "--prd") ?? "docs/PRD.md";
47
+ const prdAbs = resolve(cwd, prdRel);
48
+ const prdInitiatives = existsSync(prdAbs) ? parsePrdInitiatives(readFileSync(prdAbs, "utf8")) : [];
49
+ const mock = loadMockShapeConfig(cwd);
50
+ const designDir = resolve(cwd, mock.design_system_dir);
51
+ const lcrRoots = [resolve(cwd, mock.screens_root), resolve(cwd, mock.mock_root, "src/components")];
52
+ const files = [...new Set(lcrRoots.flatMap((r) => walkTsx(r, new Set([designDir]))))];
53
+ const lcrNodes = files.map((f) => ({
54
+ file: relative(cwd, f).replace(/\\/g, "/"),
55
+ provenance: parseLcrProvenance(readFileSync(f, "utf8")),
56
+ }));
57
+ const referenced = new Set(lcrNodes.flatMap((n) => n.provenance.filter((p) => p.kind === "story").map((p) => p.id)));
58
+ const brandPresent = existsSync(resolve(cwd, mock.design_system_dir, "tokens.ts"));
59
+ const specNodes = specs.map((s) => ({ storyId: s.story_id, prdAnchor: s.prd_ref?.anchor, sourceIssue: s.source_issue }));
60
+ const traceViolations = checkTrace({ specs: specNodes, prdAnchors: prdInitiatives.map((i) => i.anchor), lcrNodes }).violations.length;
61
+ const specFacts = specs.map((s) => ({
62
+ storyId: s.story_id,
63
+ anchored: Boolean(s.prd_ref?.anchor || s.source_issue),
64
+ addressableQuestions: s.open_questions?.addressable?.length ?? 0,
65
+ hasLcr: referenced.has(`story-${s.story_id}`),
66
+ }));
67
+ const status = computeGreenfieldStatus({
68
+ prdInitiatives: prdInitiatives.length,
69
+ specs: specFacts,
70
+ brandPresent,
71
+ traceViolations,
72
+ });
73
+ console.log("slowcook greenfield — scope status\n");
74
+ for (const st of status.stages) {
75
+ console.log(` ${st.done ? "✓" : "·"} ${st.name.padEnd(18)} ${st.detail}`);
76
+ }
77
+ console.log(`\n scope-complete: ${status.scopeComplete ? "YES ✓" : "not yet"}`);
78
+ console.log(` next: ${status.nextAction}`);
79
+ }
80
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/greenfield/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAA+B,MAAM,mBAAmB,CAAC;AAChG,OAAO,EAAE,uBAAuB,EAA2B,MAAM,aAAa,CAAC;AAE/E,SAAS,GAAG,CAAC,IAAc,EAAE,IAAY;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1C,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,OAAoB;IAChD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,cAAc;YAAE,SAAS;QACxD,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,EAAE,CAAC,WAAW,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;aAClD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAc,EAAE,WAAmB;IAClE,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;QAChF,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IAE/C,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAEnC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,aAAa,CAAC;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnG,MAAM,IAAI,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACnG,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAc,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;QAC1C,UAAU,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;KACxD,CAAC,CAAC,CAAC;IACJ,MAAM,UAAU,GAAG,IAAI,GAAG,CACxB,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAsC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAC7H,CAAC;IAEF,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC,CAAC;IAEnF,MAAM,SAAS,GAAe,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IACrI,MAAM,eAAe,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;IAEtI,MAAM,SAAS,GAAyB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,CAAC,CAAC,QAAQ;QACnB,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,YAAY,CAAC;QACtD,oBAAoB,EAAE,CAAC,CAAC,cAAc,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;QAChE,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC;KAC9C,CAAC,CAAC,CAAC;IAEJ,MAAM,MAAM,GAAG,uBAAuB,CAAC;QACrC,cAAc,EAAE,cAAc,CAAC,MAAM;QACrC,KAAK,EAAE,SAAS;QAChB,YAAY;QACZ,eAAe;KAChB,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;IACpD,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,uBAAuB,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;IACjF,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * GUCDI — `greenfield status` core (pure). Computes where a greenfield project
3
+ * is in the PRD → stories → brand → LCR → trace pipeline, and the next action.
4
+ * It is also the **scope-completeness signal**: a scope is "complete" when every
5
+ * addressable question is answered, the trace is green, every story is in the
6
+ * LCR, and the brand is set — only then does the backend phase begin.
7
+ *
8
+ * Pure + unit-tested; the IO (reading PRD/specs/brand/LCR) is in ./index.ts.
9
+ * See docs/plans/gucdi-greenfield.md.
10
+ */
11
+ export interface GreenfieldSpecFact {
12
+ storyId: string;
13
+ /** Has requirement provenance (prd_ref anchor or source_issue). */
14
+ anchored: boolean;
15
+ /** Count of unresolved *addressable* open questions (block scope-complete). */
16
+ addressableQuestions: number;
17
+ /** A mock LCR component references this story (it's been vibed). */
18
+ hasLcr: boolean;
19
+ }
20
+ export interface GreenfieldInput {
21
+ prdInitiatives: number;
22
+ specs: GreenfieldSpecFact[];
23
+ brandPresent: boolean;
24
+ traceViolations: number;
25
+ }
26
+ export interface GreenfieldStage {
27
+ name: string;
28
+ done: boolean;
29
+ detail: string;
30
+ }
31
+ export interface GreenfieldStatus {
32
+ stages: GreenfieldStage[];
33
+ scopeComplete: boolean;
34
+ /** The single next action to advance the pipeline (or the backend handoff). */
35
+ nextAction: string;
36
+ }
37
+ export declare function computeGreenfieldStatus(input: GreenfieldInput): GreenfieldStatus;
38
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/greenfield/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAqChF"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * GUCDI — `greenfield status` core (pure). Computes where a greenfield project
3
+ * is in the PRD → stories → brand → LCR → trace pipeline, and the next action.
4
+ * It is also the **scope-completeness signal**: a scope is "complete" when every
5
+ * addressable question is answered, the trace is green, every story is in the
6
+ * LCR, and the brand is set — only then does the backend phase begin.
7
+ *
8
+ * Pure + unit-tested; the IO (reading PRD/specs/brand/LCR) is in ./index.ts.
9
+ * See docs/plans/gucdi-greenfield.md.
10
+ */
11
+ export function computeGreenfieldStatus(input) {
12
+ const { prdInitiatives, specs, brandPresent, traceViolations } = input;
13
+ const anchored = specs.filter((s) => s.anchored).length;
14
+ const vibed = specs.filter((s) => s.hasLcr).length;
15
+ const addressable = specs.reduce((n, s) => n + s.addressableQuestions, 0);
16
+ const prdDone = prdInitiatives > 0;
17
+ const storiesDone = specs.length > 0 && anchored === specs.length;
18
+ const brandDone = brandPresent;
19
+ const lcrDone = specs.length > 0 && vibed === specs.length;
20
+ const traceDone = traceViolations === 0;
21
+ const questionsDone = addressable === 0;
22
+ const stages = [
23
+ { name: "PRD", done: prdDone, detail: `${prdInitiatives} initiative(s)` },
24
+ { name: "Stories (menu)", done: storiesDone, detail: `${specs.length} stories, ${anchored} anchored` },
25
+ { name: "Brand", done: brandDone, detail: brandPresent ? "design system present" : "no design system" },
26
+ { name: "LCR (vibe×eye)", done: lcrDone, detail: `${vibed}/${specs.length} stories vibed` },
27
+ { name: "Trace", done: traceDone, detail: traceViolations === 0 ? "provenance complete" : `${traceViolations} violation(s)` },
28
+ { name: "Open questions", done: questionsDone, detail: `${addressable} addressable unresolved` },
29
+ ];
30
+ const scopeComplete = prdDone && storiesDone && brandDone && lcrDone && traceDone && questionsDone;
31
+ let nextAction;
32
+ if (!prdDone)
33
+ nextAction = "Write the PRD (default docs/PRD.md), then run `slowcook menu`.";
34
+ else if (specs.length === 0)
35
+ nextAction = "Run `slowcook menu` to decompose the PRD into stories.";
36
+ else if (!storiesDone)
37
+ nextAction = `Fix provenance gaps: ${specs.length - anchored} story(ies) lack a PRD anchor / source_issue (see \`trace check\`).`;
38
+ else if (!brandDone)
39
+ nextAction = "Run `slowcook brand` to turn the brand brief into the design system.";
40
+ else if (!lcrDone) {
41
+ const next = specs.find((s) => !s.hasLcr);
42
+ nextAction = `Vibe story-${next.storyId} into the mock, then \`slowcook eye --story ${next.storyId}\` to converge it.`;
43
+ }
44
+ else if (!traceDone)
45
+ nextAction = "Resolve `trace check` violations (orphans / dangling refs).";
46
+ else if (!questionsDone)
47
+ nextAction = `Resolve ${addressable} addressable open question(s) before the scope is complete.`;
48
+ else
49
+ nextAction = "Scope complete ✓ — ready for backend: refine → recipe → brew → chef (data-source swap from the LCR's SQLite+ORM).";
50
+ return { stages, scopeComplete, nextAction };
51
+ }
52
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/commands/greenfield/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * GUCDI — pure assembler. Turns the `menu` agent's structured story drafts into
3
+ * full `Spec` objects (sequential ids, PRD back-anchor, data contract, open
4
+ * questions), and flags any draft whose prd_anchor isn't a real PRD initiative
5
+ * (a provenance gap `trace check` would later catch). The LLM dispatch +
6
+ * `writeSpec` live in ./index.ts; this part is pure + unit-tested.
7
+ */
8
+ import type { Spec } from "@slowcook-ai/core";
9
+ import type { MenuStoryDraft } from "@slowcook-ai/llm-anthropic";
10
+ export interface AssembleOptions {
11
+ /** PRD path (relative to repo root) recorded as `prd_ref.file`. */
12
+ prdFile: string;
13
+ /** First numeric story id; subsequent stories increment. */
14
+ startId: number;
15
+ /** ISO timestamp for `created_at`. */
16
+ now: string;
17
+ /** cli version → `refined_by`. */
18
+ cliVersion: string;
19
+ /** Known PRD anchors; drafts citing an unknown one are reported as gaps. */
20
+ validAnchors?: string[];
21
+ }
22
+ export interface AssembleResult {
23
+ specs: Spec[];
24
+ /** Drafts whose `prd_anchor` isn't a known PRD initiative (provenance gap). */
25
+ unanchored: {
26
+ title: string;
27
+ prd_anchor: string;
28
+ }[];
29
+ }
30
+ export declare function assembleStories(drafts: MenuStoryDraft[], opts: AssembleOptions): AssembleResult;
31
+ //# sourceMappingURL=assemble.d.ts.map