@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.
- package/README.md +22 -2
- package/dist/cli.js +30 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/brand/index.d.ts.map +1 -1
- package/dist/commands/brand/index.js +6 -0
- package/dist/commands/brand/index.js.map +1 -1
- package/dist/commands/brand/logo-cmd.d.ts +2 -0
- package/dist/commands/brand/logo-cmd.d.ts.map +1 -0
- package/dist/commands/brand/logo-cmd.js +90 -0
- package/dist/commands/brand/logo-cmd.js.map +1 -0
- package/dist/commands/brand/logo.d.ts +23 -0
- package/dist/commands/brand/logo.d.ts.map +1 -0
- package/dist/commands/brand/logo.js +60 -0
- package/dist/commands/brand/logo.js.map +1 -0
- package/dist/commands/brew/fidelity-loop.d.ts +71 -0
- package/dist/commands/brew/fidelity-loop.d.ts.map +1 -0
- package/dist/commands/brew/fidelity-loop.js +108 -0
- package/dist/commands/brew/fidelity-loop.js.map +1 -0
- package/dist/commands/brew/fidelity-phase.d.ts +49 -0
- package/dist/commands/brew/fidelity-phase.d.ts.map +1 -0
- package/dist/commands/brew/fidelity-phase.js +75 -0
- package/dist/commands/brew/fidelity-phase.js.map +1 -0
- package/dist/commands/eye/index.d.ts +2 -0
- package/dist/commands/eye/index.d.ts.map +1 -0
- package/dist/commands/eye/index.js +83 -0
- package/dist/commands/eye/index.js.map +1 -0
- package/dist/commands/eye/plan.d.ts +69 -0
- package/dist/commands/eye/plan.d.ts.map +1 -0
- package/dist/commands/eye/plan.js +87 -0
- package/dist/commands/eye/plan.js.map +1 -0
- package/dist/commands/eye/run.d.ts +42 -0
- package/dist/commands/eye/run.d.ts.map +1 -0
- package/dist/commands/eye/run.js +116 -0
- package/dist/commands/eye/run.js.map +1 -0
- package/dist/commands/eye/spec-modes.d.ts +5 -0
- package/dist/commands/eye/spec-modes.d.ts.map +1 -0
- package/dist/commands/eye/spec-modes.js +36 -0
- package/dist/commands/eye/spec-modes.js.map +1 -0
- package/dist/commands/gate/github.d.ts +27 -0
- package/dist/commands/gate/github.d.ts.map +1 -0
- package/dist/commands/gate/github.js +46 -0
- package/dist/commands/gate/github.js.map +1 -0
- package/dist/commands/gate/index.d.ts +2 -0
- package/dist/commands/gate/index.d.ts.map +1 -0
- package/dist/commands/gate/index.js +68 -0
- package/dist/commands/gate/index.js.map +1 -0
- package/dist/commands/gate/model.d.ts +55 -0
- package/dist/commands/gate/model.d.ts.map +1 -0
- package/dist/commands/gate/model.js +64 -0
- package/dist/commands/gate/model.js.map +1 -0
- package/dist/commands/gate/reviewers.d.ts +24 -0
- package/dist/commands/gate/reviewers.d.ts.map +1 -0
- package/dist/commands/gate/reviewers.js +69 -0
- package/dist/commands/gate/reviewers.js.map +1 -0
- package/dist/commands/greenfield/index.d.ts +2 -0
- package/dist/commands/greenfield/index.d.ts.map +1 -0
- package/dist/commands/greenfield/index.js +80 -0
- package/dist/commands/greenfield/index.js.map +1 -0
- package/dist/commands/greenfield/status.d.ts +38 -0
- package/dist/commands/greenfield/status.d.ts.map +1 -0
- package/dist/commands/greenfield/status.js +52 -0
- package/dist/commands/greenfield/status.js.map +1 -0
- package/dist/commands/menu/assemble.d.ts +31 -0
- package/dist/commands/menu/assemble.d.ts.map +1 -0
- package/dist/commands/menu/assemble.js +37 -0
- package/dist/commands/menu/assemble.js.map +1 -0
- package/dist/commands/menu/index.d.ts +5 -0
- package/dist/commands/menu/index.d.ts.map +1 -0
- package/dist/commands/menu/index.js +111 -0
- package/dist/commands/menu/index.js.map +1 -0
- package/dist/commands/menu/prd.d.ts +33 -0
- package/dist/commands/menu/prd.d.ts.map +1 -0
- package/dist/commands/menu/prd.js +68 -0
- package/dist/commands/menu/prd.js.map +1 -0
- package/dist/commands/plate/index.d.ts.map +1 -1
- package/dist/commands/plate/index.js +5 -1
- package/dist/commands/plate/index.js.map +1 -1
- package/dist/commands/plate/route-hint.d.ts +18 -0
- package/dist/commands/plate/route-hint.d.ts.map +1 -0
- package/dist/commands/plate/route-hint.js +21 -0
- package/dist/commands/plate/route-hint.js.map +1 -0
- package/dist/commands/recon/shape-preserve.d.ts +38 -0
- package/dist/commands/recon/shape-preserve.d.ts.map +1 -1
- package/dist/commands/recon/shape-preserve.js +112 -1
- package/dist/commands/recon/shape-preserve.js.map +1 -1
- package/dist/commands/refine/spec-yaml.d.ts +26 -0
- package/dist/commands/refine/spec-yaml.d.ts.map +1 -1
- package/dist/commands/refine/spec-yaml.js +29 -0
- package/dist/commands/refine/spec-yaml.js.map +1 -1
- package/dist/commands/run-mock/index.d.ts.map +1 -1
- package/dist/commands/run-mock/index.js +69 -16
- package/dist/commands/run-mock/index.js.map +1 -1
- package/dist/commands/run-mock/reviewer-auth-server.d.ts +40 -0
- package/dist/commands/run-mock/reviewer-auth-server.d.ts.map +1 -0
- package/dist/commands/run-mock/reviewer-auth-server.js +124 -0
- package/dist/commands/run-mock/reviewer-auth-server.js.map +1 -0
- package/dist/commands/serve/config.d.ts +28 -9
- package/dist/commands/serve/config.d.ts.map +1 -1
- package/dist/commands/serve/config.js +43 -1
- package/dist/commands/serve/config.js.map +1 -1
- package/dist/commands/serve/dev.d.ts +14 -19
- package/dist/commands/serve/dev.d.ts.map +1 -1
- package/dist/commands/serve/dev.js +46 -50
- package/dist/commands/serve/dev.js.map +1 -1
- package/dist/commands/serve/index.d.ts.map +1 -1
- package/dist/commands/serve/index.js +30 -22
- package/dist/commands/serve/index.js.map +1 -1
- package/dist/commands/serve/mock.d.ts +5 -20
- package/dist/commands/serve/mock.d.ts.map +1 -1
- package/dist/commands/serve/mock.js +44 -65
- package/dist/commands/serve/mock.js.map +1 -1
- package/dist/commands/serve/runner.d.ts +52 -0
- package/dist/commands/serve/runner.d.ts.map +1 -0
- package/dist/commands/serve/runner.js +53 -0
- package/dist/commands/serve/runner.js.map +1 -0
- package/dist/commands/serve/staging.d.ts +8 -19
- package/dist/commands/serve/staging.d.ts.map +1 -1
- package/dist/commands/serve/staging.js +53 -91
- package/dist/commands/serve/staging.js.map +1 -1
- package/dist/commands/trace/check.d.ts +67 -0
- package/dist/commands/trace/check.d.ts.map +1 -0
- package/dist/commands/trace/check.js +82 -0
- package/dist/commands/trace/check.js.map +1 -0
- package/dist/commands/trace/index.d.ts +2 -0
- package/dist/commands/trace/index.d.ts.map +1 -0
- package/dist/commands/trace/index.js +86 -0
- package/dist/commands/trace/index.js.map +1 -0
- package/dist/commands/upsert-agent-docs.d.ts.map +1 -1
- package/dist/commands/upsert-agent-docs.js +12 -0
- package/dist/commands/upsert-agent-docs.js.map +1 -1
- package/dist/commands.manifest.d.ts.map +1 -1
- package/dist/commands.manifest.js +32 -2
- package/dist/commands.manifest.js.map +1 -1
- package/dist/lib/mock-shape.d.ts +6 -0
- package/dist/lib/mock-shape.d.ts.map +1 -1
- package/dist/lib/mock-shape.js +26 -0
- package/dist/lib/mock-shape.js.map +1 -1
- 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 @@
|
|
|
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 @@
|
|
|
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":"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"}
|
|
@@ -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
|