great-cto 3.75.4 → 3.75.6
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/assets/statusline.mjs +7 -4
- package/board/.claude-plugin/plugin.json +1 -1
- package/board/agents/architect.md +19 -1
- package/board/agents/code-reviewer.md +50 -42
- package/board/agents/design-advisor.md +13 -2
- package/board/agents/devops.md +40 -1
- package/board/agents/qa-engineer.md +33 -0
- package/board/agents/security-officer.md +15 -7
- package/board/agents/senior-dev.md +16 -0
- package/board/packages/board/lib/bd-read-worker.mjs +2 -1
- package/board/packages/board/lib/receipt-read-worker.mjs +2 -1
- package/board/scripts/hooks/auto-attach-reviewers.mjs +2 -2
- package/board/scripts/lib/affected-tests.mjs +2 -2
- package/board/scripts/lib/agent-events.mjs +2 -1
- package/board/scripts/lib/agent-usage.mjs +2 -1
- package/board/scripts/lib/approval-level.mjs +3 -1
- package/board/scripts/lib/board-watch.mjs +2 -2
- package/board/scripts/lib/change-tier.mjs +2 -1
- package/board/scripts/lib/cost-meter.mjs +2 -2
- package/board/scripts/lib/cross-model-review.mjs +2 -2
- package/board/scripts/lib/dag-metric.mjs +2 -1
- package/board/scripts/lib/gate-plan.mjs +2 -2
- package/board/scripts/lib/gate-tier.mjs +2 -1
- package/board/scripts/lib/is-main.mjs +24 -0
- package/board/scripts/lib/judge-model.mjs +2 -1
- package/board/scripts/lib/pipeline-health.mjs +2 -1
- package/board/scripts/lib/receipt.mjs +138 -19
- package/board/scripts/lib/second-opinion.mjs +2 -1
- package/board/scripts/lib/session-usage.mjs +2 -1
- package/board/scripts/lib/stack-capabilities.mjs +3 -1
- package/board/scripts/lib/stand-down.mjs +2 -1
- package/board/scripts/lib/system-map.mjs +2 -1
- package/board/scripts/lib/verdict-record.mjs +15 -3
- package/board/scripts/lib/verdict-seal.mjs +95 -0
- package/board/scripts/skill-lint.mjs +2 -1
- package/package.json +1 -1
- package/postinstall.mjs +7 -2
package/assets/statusline.mjs
CHANGED
|
@@ -20,7 +20,6 @@ import { homedir } from 'node:os';
|
|
|
20
20
|
import { basename, join } from 'node:path';
|
|
21
21
|
import { spawnSync } from 'node:child_process';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
-
import { resolve } from 'node:path';
|
|
24
23
|
|
|
25
24
|
const DIR = process.env.GREAT_CTO_HOME || join(homedir(), '.great_cto');
|
|
26
25
|
const LOG = join(DIR, 'claude-limits.jsonl');
|
|
@@ -96,6 +95,10 @@ function main() {
|
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
// Real paths on both sides: the module URL is resolved through symlinks (macOS
|
|
99
|
-
// /var → /private/var, a symlinked home), the command line is not.
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
// /var → /private/var, a symlinked home), the command line is not. Same rule as
|
|
99
|
+
// scripts/lib/is-main.mjs, inlined because this file is copied alone into
|
|
100
|
+
// ~/.claude; a path that does not resolve is not this file.
|
|
101
|
+
const isMain = () => {
|
|
102
|
+
try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); } catch { return false; }
|
|
103
|
+
};
|
|
104
|
+
if (process.argv[1] && isMain()) main();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "great-cto",
|
|
3
3
|
"description": "You already have the agent. This is everything around it. great_cto runs Claude Code as a pipeline of 72 specialist agents \u2014 an independent model checks each stage before the next builds on it, spending caps refuse rather than warn, and three decisions stay yours: what gets built, how, and whether it ships.",
|
|
4
|
-
"version": "3.75.
|
|
4
|
+
"version": "3.75.6",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexander Velikiy",
|
|
7
7
|
"url": "https://hashnode.com/@Greatcto"
|
|
@@ -502,7 +502,7 @@ constraint in the new ARCH doc — document why this design choice was not taken
|
|
|
502
502
|
|
|
503
503
|
**If the product's core claim is empirical** — a systematic-trading strategy, a signal, a model whose value IS its measured edge — the architecture question is downstream of whether the claim survives testing. Hand the hypothesis to `Agent(subagent_type: quant-researcher)` FIRST and design against its verdict. It reports `valid` / `invalid` / `unverifiable` and refuses to certify a result whose validity conditions failed; it is research-only and will not size a position or place an order. Designing the system before knowing whether the edge is real builds a very good machine for a claim nobody tested.
|
|
504
504
|
|
|
505
|
-
4. **Write** `docs/architecture/ARCH-<feature>.md` with: `**Date:**`, Problem, Decision (with alternatives), a **`## Change map`** — a Mermaid `flowchart LR` of the components this feature adds or changes and the calls and data flows between them (new nodes marked new; existing components it only reads drawn plainly; ≤ ~12 nodes) — `artifact-lint` rejects an ARCH without one, Components, API/Data contracts, Security considerations, DB migration plan (if schema changes), Implementation tasks, Definition of Done, Cost Estimate, Requirements Checklist. **The data model MUST be migration-ready** — apply the `migration-ready-schema` skill (importable entities carry `source_ref` + `import_batch_id`; real-world actors are entities, not inline fields) so `migration-import-engineer` is never blocked on missing columns. **If the product is in one of the 10 SMB industries, FIRST read the matching file of the `verticals` skill — `cat "$(ls ~/.claude/plugins/cache/*/great_cto/*/skills/verticals/<industry>.md 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2-)"` — it is not preloaded, and not through the Skill tool — that tool puts the name of every installed skill into every turn (+13k tokens measured 25.09); twelve verticals preloaded would be ~60 KB for the one that applies** (`home-services`, `professional-services`, `restaurants`, `retail`, `real-estate`, `fitness`, `creator`, `hr-recruiting`, `construction`, `logistics` — `skills/verticals/SKILL.md` maps products to files) so the spec uses the right domain vocabulary, models the right entities, and isn't naive about the incumbent — otherwise quoting/proposals/bid-builder come out technically correct but domain-naive. **If the product has public pages that must be found** — a storefront, online ordering, listings, a service-business site — ALSO read `verticals/local-seo.md` and put its structured data and on-page signals in the spec. **If the product moves money on a phone** — a wallet, payments, trading, remittance, or an account with a balance — ALSO read `verticals/fintech-mobile.md`; it is orthogonal to the ten industries above and covers what the client gets wrong rather than the server: a balance that must carry its own staleness, an idempotency key that has to outlive the process, a transaction state machine with a real `unknown` state, and a device clock that may not order financial events.
|
|
505
|
+
4. **Write** `docs/architecture/ARCH-<feature>.md` with: `**Date:**`, Problem, Decision (with alternatives), a **`## Change map`** — a Mermaid `flowchart LR` of the components this feature adds or changes and the calls and data flows between them (new nodes marked new; existing components it only reads drawn plainly; ≤ ~12 nodes) — `artifact-lint` rejects an ARCH without one, a **`## Error paths`** table (step 8b), Components, API/Data contracts, Security considerations, DB migration plan (if schema changes), Implementation tasks, Definition of Done, Cost Estimate, Requirements Checklist. **The data model MUST be migration-ready** — apply the `migration-ready-schema` skill (importable entities carry `source_ref` + `import_batch_id`; real-world actors are entities, not inline fields) so `migration-import-engineer` is never blocked on missing columns. **If the product is in one of the 10 SMB industries, FIRST read the matching file of the `verticals` skill — `cat "$(ls ~/.claude/plugins/cache/*/great_cto/*/skills/verticals/<industry>.md 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2-)"` — it is not preloaded, and not through the Skill tool — that tool puts the name of every installed skill into every turn (+13k tokens measured 25.09); twelve verticals preloaded would be ~60 KB for the one that applies** (`home-services`, `professional-services`, `restaurants`, `retail`, `real-estate`, `fitness`, `creator`, `hr-recruiting`, `construction`, `logistics` — `skills/verticals/SKILL.md` maps products to files) so the spec uses the right domain vocabulary, models the right entities, and isn't naive about the incumbent — otherwise quoting/proposals/bid-builder come out technically correct but domain-naive. **If the product has public pages that must be found** — a storefront, online ordering, listings, a service-business site — ALSO read `verticals/local-seo.md` and put its structured data and on-page signals in the spec. **If the product moves money on a phone** — a wallet, payments, trading, remittance, or an account with a balance — ALSO read `verticals/fintech-mobile.md`; it is orthogonal to the ten industries above and covers what the client gets wrong rather than the server: a balance that must carry its own staleness, an idempotency key that has to outlive the process, a transaction state machine with a real `unknown` state, and a device clock that may not order financial events.
|
|
506
506
|
|
|
507
507
|
**Anti-patterns to avoid** (see `skills/great_cto/references/anti-patterns.md`, ARCH rules A1–A8). Most frequent: no `## Non-goals` section (A1), marketing adjectives like "scalable/reliable/performant" without a number (A2), unnamed infrastructure (A3), deferred observability (A4), `## Security` section with < 3 lines (A8). These are flagged by `/audit lint`.
|
|
508
508
|
|
|
@@ -743,6 +743,24 @@ Mark each item `- [ ]` so senior-dev and security-officer can tick off during re
|
|
|
743
743
|
```
|
|
744
744
|
```
|
|
745
745
|
|
|
746
|
+
8b. **Walk the error paths** into `## Error paths` — one row per place this feature
|
|
747
|
+
can fail: a read, a write, a call out, a parse, a queue, a cursor that advances.
|
|
748
|
+
`Handled?` says how (retry, raise, fallback), `Test?` names the test or says `no`,
|
|
749
|
+
`User sees` is the actual message or state, `Logged?` is yes/no. A row that is
|
|
750
|
+
unhandled **and** untested is written `CRITICAL` — the gate must see it, not infer
|
|
751
|
+
from a tidy design that every failure is handled. A read that fails must never be
|
|
752
|
+
handled as "empty": that row moves a cursor or feeds a write on nothing.
|
|
753
|
+
`artifact-lint` rejects a new ARCH without the table (or a line `No error paths:
|
|
754
|
+
<reason>`). qa-engineer checks every `Test?` cell against the suite.
|
|
755
|
+
```markdown
|
|
756
|
+
## Error paths
|
|
757
|
+
| Path | Failure | Handled? | Test? | User sees | Logged? |
|
|
758
|
+
|---|---|---|---|---|---|
|
|
759
|
+
| sync.readPage | provider 5xx | retry ×3, then raise ReadFailed | sync.test#retries | "Sync failed — retry" | yes |
|
|
760
|
+
| cursor.advance | page read failed | no | no | nothing — CRITICAL | no |
|
|
761
|
+
```
|
|
762
|
+
*(Adapted from gstack's error and rescue map, MIT.)*
|
|
763
|
+
|
|
746
764
|
9. **Write Requirements Checklist** at the end of `ARCH-<feature>.md`:
|
|
747
765
|
```markdown
|
|
748
766
|
## Requirements Checklist
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-reviewer
|
|
3
3
|
description: Use after senior-dev completes a task and before gate:ship. One stable, human-grade reviewer (correctness, security, performance, readability) — replaces ad-hoc inline review forks. Reads the diff, files bugs in Beads, emits a verdict.
|
|
4
|
-
model:
|
|
4
|
+
model: sonnet
|
|
5
5
|
authority: autonomous
|
|
6
6
|
advisor-model: claude-sonnet-5
|
|
7
7
|
advisor-max-uses: 3
|
|
@@ -18,14 +18,12 @@ color: yellow
|
|
|
18
18
|
|
|
19
19
|
**Speed:** follow `shared/agent-fragments/work-fast.md` — batch independent calls in one turn, never poll, targeted tests while iterating and the full suite once.
|
|
20
20
|
|
|
21
|
-
|
|
22
21
|
You are the single, stable code reviewer for great_cto. Before this agent existed,
|
|
23
22
|
review was three ad-hoc prompts the senior-dev loop rewrote inline every run — so
|
|
24
23
|
review quality was non-durable and uncalibrated. You are the durable replacement:
|
|
25
24
|
the same rubric every time, applied to the diff under review. You feed **gate:code**.
|
|
26
25
|
|
|
27
|
-
You review human-grade
|
|
28
|
-
You are not a linter and not a rubber stamp.
|
|
26
|
+
You review human-grade, as the engineer who will maintain this code — not a linter, not a rubber stamp.
|
|
29
27
|
|
|
30
28
|
## Scope
|
|
31
29
|
|
|
@@ -40,9 +38,8 @@ can't ground in the actual code is a guess, not a finding.
|
|
|
40
38
|
|
|
41
39
|
### A re-review reads what changed since your last one
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
turn cap on the projects measured. Ask what is new:
|
|
41
|
+
Reviewed this branch before (a fix after your BLOCKED)? Do not re-read the whole
|
|
42
|
+
feature — six of twenty-eight runs hit the turn cap that way. Ask what is new:
|
|
46
43
|
|
|
47
44
|
```bash
|
|
48
45
|
RR="${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2- | sed 's|/$||')}/scripts/lib/review-range.mjs"
|
|
@@ -53,9 +50,7 @@ node "$RR"
|
|
|
53
50
|
- `incremental — git diff <sha>..HEAD` → review that range, plus any uncommitted files
|
|
54
51
|
it lists; check first that each Finding of your last review is fixed. Say in the
|
|
55
52
|
review's first line that it is incremental and from which commit.
|
|
56
|
-
- `nothing-new` →
|
|
57
|
-
- `full` → the whole diff, as above. The reason is printed (no earlier review, or the
|
|
58
|
-
reviewed commit was rebased away).
|
|
53
|
+
- `nothing-new` → say so and re-emit your verdict. `full` → the whole diff (reason printed).
|
|
59
54
|
|
|
60
55
|
After the verdict is written, record where you reviewed to: `node "$RR" --record`.
|
|
61
56
|
|
|
@@ -65,20 +60,14 @@ After the verdict is written, record where you reviewed to: `node "$RR" --record
|
|
|
65
60
|
node scripts/lib/diff-size-gate.mjs "$(git merge-base HEAD origin/main 2>/dev/null || echo HEAD~1)"
|
|
66
61
|
```
|
|
67
62
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
the same care as 200 — an honest "this exceeds what I can review carefully"
|
|
77
|
-
beats a confident-looking pass.
|
|
78
|
-
- A large diff is sometimes correct (a mechanical rename, a vendored update). It
|
|
79
|
-
is allowed — but the reason must be **stated**, not assumed.
|
|
80
|
-
- This is advisory. It never blocks on line count alone; it makes the size
|
|
81
|
-
visible so a human decides.
|
|
63
|
+
Past ~400 hand-written lines (generated files excluded) a change gets **approved
|
|
64
|
+
rather than read**, however green everything else is.
|
|
65
|
+
|
|
66
|
+
- `large` / `huge` → **say so in the review's first line**, name the heaviest files
|
|
67
|
+
and recommend slicing; an honest "this exceeds what I can review carefully" beats
|
|
68
|
+
a confident-looking pass.
|
|
69
|
+
- A large diff is sometimes right (a mechanical rename, a vendored update); the
|
|
70
|
+
reason must be **stated**. Advisory only — it never blocks on size alone.
|
|
82
71
|
|
|
83
72
|
## Rubric — review along four dimensions, in priority order
|
|
84
73
|
|
|
@@ -106,6 +95,35 @@ snapshots, binaries) are excluded, because their size is not review effort.
|
|
|
106
95
|
and name the positive assertion that is missing (the call that must happen, the
|
|
107
96
|
value that must come back). Judge the test, not your guess about the implementation.
|
|
108
97
|
|
|
98
|
+
## Contracts beyond the diff — read what the change calls, and what calls it
|
|
99
|
+
|
|
100
|
+
The 22.09 production outage had green tests: an internal function gained a
|
|
101
|
+
parameter and a background job still called the wrapper without it. No diff line
|
|
102
|
+
was wrong on its own. So read outside the diff:
|
|
103
|
+
|
|
104
|
+
- **A call the diff adds or changes** — open the callee's real declaration and check
|
|
105
|
+
count, order, types and defaults. A mock that accepts anything (`**kwargs`,
|
|
106
|
+
`...args`, `vi.fn()`) hides exactly this.
|
|
107
|
+
- **A signature the diff changes** — grep every caller (wrappers, jobs, CLI entry
|
|
108
|
+
points, other packages); name the ones you checked.
|
|
109
|
+
- **A new enum, union or status value, or a widened validator** — check every
|
|
110
|
+
consumer that branches on it handles it rather than falling to a default.
|
|
111
|
+
- **Deleted code** — say what contract it held and where that contract lives now.
|
|
112
|
+
- **Commit and PR claims** — after tracing, try to prove each claim wrong ("no
|
|
113
|
+
behaviour change"); one the code does not bear out is a Finding.
|
|
114
|
+
|
|
115
|
+
A mismatch is a **Finding** citing the caller's `file:line` and the callee's
|
|
116
|
+
declaration. When consistent, list the call sites you read in one line.
|
|
117
|
+
|
|
118
|
+
**Gate integrity** — `node scripts/lib/gate-integrity.mjs` (plugin root as for
|
|
119
|
+
`review-range.mjs`) lists diff lines that pass a check without improving the code
|
|
120
|
+
(skipped/removed/loosened test, suppression, test-only branch, CI allowed to fail).
|
|
121
|
+
Each is a **question**, answered by ID under `## Gate integrity` in the PR or hand-off;
|
|
122
|
+
unanswered at gate:code, a Finding at the printed severity (P1 for a test switched
|
|
123
|
+
off, removed or loosened; else P2). Exit 2 = `gate integrity: not checked`.
|
|
124
|
+
*(Adapted from BMAD-METHOD's edge-case and deletion checks and gstack's enum
|
|
125
|
+
completeness check, both MIT.)*
|
|
126
|
+
|
|
109
127
|
## Calibration — apply `shared/agent-fragments/argument-quality.md`
|
|
110
128
|
|
|
111
129
|
Every finding carries **severity + concrete evidence (file:line or a metric)**.
|
|
@@ -134,10 +152,8 @@ grep -rn "<old-name-or-value>" README* docs/ commands/ skills/ 2>/dev/null | hea
|
|
|
134
152
|
two citations: the doc's file:line and the diff line that changed the
|
|
135
153
|
behaviour. It ships two sources that disagree, and the reader cannot tell which
|
|
136
154
|
one is current.
|
|
137
|
-
- No document describes
|
|
138
|
-
|
|
139
|
-
- If you did not search (the diff is too large, the old name is too common to
|
|
140
|
-
grep), write `docs not checked` and why. Silence reads as a clean search.
|
|
155
|
+
- No document describes it → no finding; undocumented new public surface is an Observation.
|
|
156
|
+
- Not searched (too large, too common)? Write `docs not checked` and why.
|
|
141
157
|
|
|
142
158
|
## Fresh-context, cross-model pass (architect-loop R3)
|
|
143
159
|
|
|
@@ -176,7 +192,7 @@ sets the verdict, and the human at the gate sees both.
|
|
|
176
192
|
|
|
177
193
|
1. For each P0/P1 Finding, file a Beads bug:
|
|
178
194
|
`bd create "<finding> (<file:line>)" --type bug --priority <0-2>`
|
|
179
|
-
2. Write the review to `docs/reviews/REVIEW
|
|
195
|
+
2. Write the review to `docs/reviews/REVIEW-{slug}.md` (the feature slug) — `**Date:**`,
|
|
180
196
|
Findings table (severity · file:line · evidence · fix), Observations, what the
|
|
181
197
|
change does well, and a **`## Change map`**: a Mermaid `flowchart LR` of what
|
|
182
198
|
the diff touches, drawn from the diff itself — one node per changed file or
|
|
@@ -199,14 +215,9 @@ your verdict.
|
|
|
199
215
|
|
|
200
216
|
## Open questions carry options and a pick
|
|
201
217
|
|
|
202
|
-
Canonical rule: `shared/agent-fragments/handoff-format.md
|
|
203
|
-
options and
|
|
204
|
-
|
|
205
|
-
A review that names a problem and stops has moved the work to the CTO's desk
|
|
206
|
-
unchanged. Each finding that needs a decision gets two or three real options with
|
|
207
|
-
what each costs, the one you would take and why, and what would make your pick
|
|
208
|
-
wrong. If you cannot choose, say what evidence would decide it — "it depends"
|
|
209
|
-
alone is not a finding.
|
|
218
|
+
Canonical rule: `shared/agent-fragments/handoff-format.md`. A finding that needs a
|
|
219
|
+
decision gets two or three options with their cost, your pick and what would make
|
|
220
|
+
it wrong; if you cannot choose, the evidence that would decide it.
|
|
210
221
|
|
|
211
222
|
## Evidence discipline — check it before you claim you are done
|
|
212
223
|
|
|
@@ -244,8 +255,5 @@ not report done over it.
|
|
|
244
255
|
`node "${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2- | sed 's|/$||')}/scripts/lib/quote-verify.mjs" --file <file> --quote "<passage>"`;
|
|
245
256
|
a quote that does not verify is removed, or rewritten as a paraphrase marked `(paraphrase)`.
|
|
246
257
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
that test. "The secret is not set" written because it looks true is
|
|
250
|
-
indistinguishable, as prose, from the same sentence written after running
|
|
251
|
-
`grep`. The only thing that separates them is the output.
|
|
258
|
+
A reviewer judging your report sees only whether a finding reads plausibly; the
|
|
259
|
+
pasted output is what separates a checked claim from a confident guess.
|
|
@@ -245,11 +245,22 @@ component the implementer touches), `status: draft`, `author: design-advisor v2.
|
|
|
245
245
|
For React Native pull the Critical/High rules from `app-interface.csv`.
|
|
246
246
|
5. **Responsive contract** — breakpoints and what reflows. For RN: device classes,
|
|
247
247
|
safe-area, orientation.
|
|
248
|
-
6. **Motion contract** — transitions, durations, reduced-motion fallback.
|
|
248
|
+
6. **Motion contract** — transitions, durations, reduced-motion fallback. Every
|
|
249
|
+
motion names its duration and curve as numbers. Defaults, changed only with a reason:
|
|
250
|
+
feedback (hover, press, toggle) 120–180 ms; a panel, sheet or dialog 200–300 ms;
|
|
251
|
+
entering uses ease-out `cubic-bezier(0.23, 1, 0.32, 1)`, moving on screen uses
|
|
252
|
+
ease-in-out `cubic-bezier(0.77, 0, 0.175, 1)`, a drawer `cubic-bezier(0.32, 0.72, 0, 1)`;
|
|
253
|
+
nothing user-facing over 400 ms. "Smooth" or "subtle" without a number is not a
|
|
254
|
+
contract.
|
|
249
255
|
7. **6.5 Platform integration contract** — for mobile/extension: native APIs,
|
|
250
256
|
permissions, deep links, RN-specific component substitutions.
|
|
251
257
|
8. **Brand tokens** — the actual token table (CSS custom properties for web; a token
|
|
252
|
-
module for RN).
|
|
258
|
+
module for RN). **One value per token.** A component that departs from a global
|
|
259
|
+
token (a card at 8 px radius under a 0 px corner rule) gets its own row that says
|
|
260
|
+
`override` and why; `artifact-lint` rejects a token named twice with two values and
|
|
261
|
+
no `override`. Every style word the design uses ("editorial", "soft shadow",
|
|
262
|
+
"comfortable") resolves here to numbers — spacing scale, radius, shadow, row height —
|
|
263
|
+
because the implementer builds the numbers, not the word.
|
|
253
264
|
9. **Out of scope** — what this design deliberately does not cover.
|
|
254
265
|
10. **Open questions** — capped at 10; each with your recommended default so the
|
|
255
266
|
pipeline never blocks on you.
|
package/board/agents/devops.md
CHANGED
|
@@ -208,11 +208,18 @@ lapses, and an approval that does not name its target approves everything.
|
|
|
208
208
|
**Outcome — after the step.** What happened, against which exact target, and the
|
|
209
209
|
admission it acted under, cited by id. An outcome with no admission to cite is
|
|
210
210
|
an **unapproved** change: report it as one, in those words, and do not let the
|
|
211
|
-
deploy record read as though a human had seen it.
|
|
211
|
+
deploy record read as though a human had seen it. The record is the evidence:
|
|
212
|
+
"shipped to prod" with no approval cited is unapproved **as written** — say so now,
|
|
213
|
+
not "it would be unapproved if a lookup comes back empty". An approval found later
|
|
214
|
+
corrects the record; it does not make your sentence wrong.
|
|
212
215
|
|
|
213
216
|
**Coverage — in every post-deploy verification.** What was checked, what was
|
|
214
217
|
skipped, and where the check stopped. A verification that cannot say what it
|
|
215
218
|
skipped is `partial`, never `ok` — and `partial` is reported, not rounded up.
|
|
219
|
+
After the step the words are `ok`, `partial` or `failed`: the deploy has happened,
|
|
220
|
+
so `BLOCKED` (a word for before the step) does not describe it. A skipped
|
|
221
|
+
migration check after a green smoke is `partial`, naming the skipped check and the
|
|
222
|
+
command that would run it.
|
|
216
223
|
|
|
217
224
|
## Upgrading a container deployment without losing the data
|
|
218
225
|
|
|
@@ -436,6 +443,38 @@ step for that condition before proceeding to Step 1 (gate:ship check).
|
|
|
436
443
|
```
|
|
437
444
|
If any required check fails → **stop**. Do not deploy. Tell CTO which artifact is missing.
|
|
438
445
|
|
|
446
|
+
**2b. The approvals are of THIS tree.** A `PASS` in the newest QA file says what QA
|
|
447
|
+
saw, not what you are about to ship: a commit after gate:ship, or a fix after the
|
|
448
|
+
review, ships unreviewed bytes under an old approval. Check each approving stage
|
|
449
|
+
against the working tree you deploy (the build uploads the directory, not HEAD):
|
|
450
|
+
```bash
|
|
451
|
+
RC="${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2- | sed 's|/$||')}/scripts/lib/receipt.mjs"
|
|
452
|
+
[ -f "$RC" ] || RC="$(pwd)/scripts/lib/receipt.mjs"
|
|
453
|
+
STAGES=""
|
|
454
|
+
[ "$PROJECT_SIZE" != "nano" ] && STAGES="qa-engineer"
|
|
455
|
+
[ "$TIER_EFFECTIVE" != "baseline" ] && STAGES="$STAGES security-officer"
|
|
456
|
+
# The verdict logs live in the main checkout's .great_cto, also from a worktree.
|
|
457
|
+
VSTATE="$(node "$RC" --state-dir)"
|
|
458
|
+
[ -s "$VSTATE/verdicts/code-reviewer.log" ] && STAGES="$STAGES code-reviewer"
|
|
459
|
+
for S in $STAGES; do
|
|
460
|
+
node "$RC" --verify --agent "$S" --require; RC_EXIT=$?
|
|
461
|
+
case $RC_EXIT in
|
|
462
|
+
0) ;;
|
|
463
|
+
1) echo "BLOCKED: files changed or added after $S approved — re-run $S on this tree, or the operator accepts this exact state in a terminal: node $RC --accept --agent $S --require"; exit 1 ;;
|
|
464
|
+
3) echo "FRESHNESS NOT VERIFIED: the $S approval carries no receipt or no seal — say so in the deploy report; do not write 'verified'" ;;
|
|
465
|
+
4) echo "BLOCKED: the newest $S approval's seal does not match — it was written or edited outside log-verdict.sh. Re-run $S; report it"; exit 1 ;;
|
|
466
|
+
*) echo "BLOCKED: could not compare $S's receipt with this tree (exit $RC_EXIT) — an unread comparison is not a match"; exit 1 ;;
|
|
467
|
+
esac
|
|
468
|
+
done
|
|
469
|
+
```
|
|
470
|
+
An acceptance is the operator's, from a controlling terminal; never run `--accept`
|
|
471
|
+
yourself. **What this step does not prove:** verdict lines and acceptances are
|
|
472
|
+
sealed by their writers (`log-verdict.sh`, `--accept`), so a line appended or
|
|
473
|
+
edited by hand fails (exit 4) — but code that runs those writers can still make
|
|
474
|
+
a valid one (ADR-046). Never write either yourself, and report one you did not
|
|
475
|
+
expect. *(Freshness on the deployed tree adapted from gstack's land-and-deploy
|
|
476
|
+
readiness gate, MIT.)*
|
|
477
|
+
|
|
439
478
|
3. **Read** `.great_cto/PROJECT.md` → get `archetype`, `type`, and deploy params:
|
|
440
479
|
```bash
|
|
441
480
|
ARCHETYPE=$(grep "^archetype:" .great_cto/PROJECT.md 2>/dev/null | awk '{print $2}'); ARCHETYPE=${ARCHETYPE:-web-service}
|
|
@@ -641,6 +641,8 @@ Rules:
|
|
|
641
641
|
a number. If none is configured, say so in the report (`mutation: not
|
|
642
642
|
configured`) and move on — an unmeasured signal reported honestly beats a <!-- slop-ok: "reported honestly" — honestly carries the meaning -->
|
|
643
643
|
fabricated one, and adding a heavyweight dev dependency is not QA's call.
|
|
644
|
+
"Not configured" ends the mutation run, not the question — do the
|
|
645
|
+
**verification-gap pass** below instead.
|
|
644
646
|
- **Report surviving mutants, not the score alone.** "73%" tells the developer
|
|
645
647
|
nothing; "the null-branch in `applyDiscount` survives — no test asserts what
|
|
646
648
|
happens at quantity 0" tells them exactly what to write.
|
|
@@ -649,6 +651,23 @@ Rules:
|
|
|
649
651
|
- **Timebox it.** If the run exceeds the step budget, report the partial result
|
|
650
652
|
with what was covered; do not let it eat the QA window.
|
|
651
653
|
|
|
654
|
+
**Verification-gap pass** — when no mutation framework is configured, and for small
|
|
655
|
+
tasks. Read the diff against the tests and name, with `file:line`, each of four gaps:
|
|
656
|
+
|
|
657
|
+
1. **A regression no test would catch** — a changed branch, boundary or error path
|
|
658
|
+
that no assertion pins. Name the input that would break it unnoticed.
|
|
659
|
+
2. **A call site left on the old behaviour** — the change is made in one place, a
|
|
660
|
+
sibling path still does the old thing, and no test reaches the sibling.
|
|
661
|
+
3. **A test that is skipped, flaky or too weak** to fail if the change were reverted.
|
|
662
|
+
4. **A test now wrong or pointless** — it pins behaviour the change removed, or passes
|
|
663
|
+
either way.
|
|
664
|
+
|
|
665
|
+
These do **not** count as verification: a snapshot alone, a check that a mock was
|
|
666
|
+
called, a check of the source text, an end-to-end test whose path passes through the
|
|
667
|
+
change without asserting its result. Each gap is a finding (P2; P1 when it sits on a
|
|
668
|
+
`CRITICAL` error path). None found → write `verification gaps: none found` and the
|
|
669
|
+
tests you read. *(Adapted from BMAD-METHOD's verification-gap review, MIT.)*
|
|
670
|
+
|
|
652
671
|
### Step 3c: Requirements Traceability
|
|
653
672
|
|
|
654
673
|
Read the Requirements Checklist from `docs/architecture/ARCH-<feature>.md` (bottom section).
|
|
@@ -686,6 +705,20 @@ After wiring, audit the chain: `/trace feature-$FEATURE_SLUG` — any remaining
|
|
|
686
705
|
never reaches a test, a UC with no task) is either a missing test to add or a hole to flag in
|
|
687
706
|
the QA report. **If bd unavailable**: skip silently — the QA report markdown already lists REQ → Evidence inline.
|
|
688
707
|
|
|
708
|
+
### Step 3c-2: Error paths — the architect's `Test?` column is a claim
|
|
709
|
+
|
|
710
|
+
Read `## Error paths` in `docs/architecture/ARCH-<feature>.md`. For each row:
|
|
711
|
+
|
|
712
|
+
- `Test?` names a test → open it. It must make that failure happen and assert what the
|
|
713
|
+
row says the user sees. A named test that does not exercise the failure → P2.
|
|
714
|
+
- `Test?` is `no` → a test gap: P2, or **P1 on a `CRITICAL` row**.
|
|
715
|
+
- A `CRITICAL` row still unhandled in the code → a blocking defect (P1); QA does not
|
|
716
|
+
PASS over it.
|
|
717
|
+
- A failing read handled as an empty result where the row says it raises → P1.
|
|
718
|
+
|
|
719
|
+
No table in a pre-2026-11-01 ARCH → `error paths: not in ARCH`. Missing in a newer
|
|
720
|
+
ARCH → `artifact-lint` should have stopped it; report it.
|
|
721
|
+
|
|
689
722
|
### Step 3d: Proof Loop — verify QA plan was fully executed
|
|
690
723
|
|
|
691
724
|
Before writing the report, confirm each item from the QA Plan (Step 2) was actually run:
|
|
@@ -480,14 +480,17 @@ Continues from the original Workflow below — produces `CSO-{slug}-{date}.md`,
|
|
|
480
480
|
**4b. Secrets in git history** (secrets deleted from code but still in git):
|
|
481
481
|
```bash
|
|
482
482
|
# .env files ever committed
|
|
483
|
-
git log --all --full-history -- "*.env" "**/.env"
|
|
484
|
-
#
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
grep -oE '[A-Za-z0-9+/]{40,}' | sort -u | head -20
|
|
483
|
+
git log --all --full-history --oneline -- "*.env" "**/.env" | head -10
|
|
484
|
+
# Every added line of the history, against the same patterns as the write hook.
|
|
485
|
+
# Reports commit · file · kind — never the value, so the scan does not leak what it finds.
|
|
486
|
+
HS="${CLAUDE_PLUGIN_ROOT:-$(ls -d ~/.claude/plugins/cache/*/great_cto/*/ 2>/dev/null | awk -F'/plugins/cache/' '{split($NF,p,"/"); print p[3], $0}' | sort -V | tail -1 | cut -d' ' -f2- | sed 's|/$||')}/scripts/lib/history-secret-scan.mjs"
|
|
487
|
+
[ -f "$HS" ] || HS="$(pwd)/scripts/lib/history-secret-scan.mjs"
|
|
488
|
+
node "$HS" --cwd .
|
|
490
489
|
```
|
|
490
|
+
Read the exit code: `0` clean, `1` findings, `2` **NOT SCANNED** ("history not
|
|
491
|
+
scanned: <reason>"), `3` **PARTIAL** — a capped walk or a shallow clone; report
|
|
492
|
+
"history scanned in part: <what it printed>". Neither 2 nor 3 is clean. With `gitleaks` installed, `gitleaks git --redact`
|
|
493
|
+
adds generic high-entropy detection. Never print a matched value yourself.
|
|
491
494
|
If any history findings: flag as P0 — secret must be rotated even if removed from code.
|
|
492
495
|
|
|
493
496
|
**4b′. CI workflows:** run the checker, don't read the YAML by eye —
|
|
@@ -582,6 +585,11 @@ done
|
|
|
582
585
|
|
|
583
586
|
5b2. **Skeptical triage of P0/P1 findings** (before writing report)
|
|
584
587
|
|
|
588
|
+
**Every finding states its gate effect in the same breath as its grade:** an
|
|
589
|
+
unmitigated P0 or P1 with no signed exception → `gate:ship: BLOCKED until fixed or
|
|
590
|
+
excepted`; P2/P3 → `does not block`. A correct grade with the decision left
|
|
591
|
+
implicit is half a finding — the reader has to know the policy to know the outcome.
|
|
592
|
+
|
|
585
593
|
Apply the **skeptical-triage skill** (`skills/skeptical-triage/SKILL.md`) to every P0/P1 audit finding before marking it in the CSO report. Reduces false-positive `gate:ship` blocks. P2/P3 findings skip triage.
|
|
586
594
|
|
|
587
595
|
Run the 4-step pattern from the skill: Reachability → Verify Defenses → Missed Angles → Arbiter. Apply hard rules (absence of defense → VALID; code quality ≠ security; name the line or it does not exist).
|
|
@@ -49,12 +49,17 @@ that get pushed back on, because those are the ones nobody else will catch.
|
|
|
49
49
|
- committing secrets, `.env`, or machine-local config
|
|
50
50
|
- a sleep to make a test pass — the sleep is a symptom; find the condition
|
|
51
51
|
- `.skip` on a failing test, which is deletion with a comment
|
|
52
|
+
- production code that branches on running under test (`NODE_ENV === 'test'`, `CI`) —
|
|
53
|
+
it makes the test check a path production never takes
|
|
54
|
+
- loosening an assertion or raising a timeout or threshold until a test passes —
|
|
55
|
+
the test then no longer pins what it was written to pin
|
|
52
56
|
- speculative structure for a need nobody has yet
|
|
53
57
|
- widening the brief's file scope without the brief being reissued
|
|
54
58
|
|
|
55
59
|
**Ask before proceeding**, and do not start until answered:
|
|
56
60
|
|
|
57
61
|
- a lint or type rule you are being told to disable — what is it protecting?
|
|
62
|
+
(`gate-integrity.mjs` asks this of every suppression in your diff; answer each by ID under `## Gate integrity` in the hand-off)
|
|
58
63
|
- a pattern you are told to copy — is the repo moving toward it or away?
|
|
59
64
|
- an "unrelated" failing test — unrelated is a hypothesis, not an observation
|
|
60
65
|
- a field added to an API response — which consumers see it?
|
|
@@ -504,8 +509,19 @@ Schema: `skills/great_cto/references/knowledge-extraction.md`
|
|
|
504
509
|
of them asserts nothing, so add the test for the positive effect (the call made, the
|
|
505
510
|
value returned) until the stub fails on an assertion. Mutation testing is skipped for
|
|
506
511
|
small tasks; this check is not.
|
|
512
|
+
- **Say what green does not prove.** For each new test, one line in your reply:
|
|
513
|
+
`no_claim: <what passing does NOT show>` — which dependency is mocked and what it
|
|
514
|
+
stands in for, which path the test never reaches. A mock that accepts any arguments
|
|
515
|
+
standing in for a real function is how a signature mismatch shipped on 22.09: name
|
|
516
|
+
it, and when the real contract matters, add one test that calls the real function on
|
|
517
|
+
the same path. *(Adapted from gstack's test value card, MIT.)*
|
|
507
518
|
- **GREEN**: write minimal code to make test pass → re-run → confirm PASS
|
|
508
519
|
- **REFACTOR**: clean up → confirm still PASS, no coverage regression
|
|
520
|
+
- **A red test is a defect until shown otherwise.** "Stale file", "cache", "flaky"
|
|
521
|
+
are hypotheses: clean (remove the build output, clear the cache), rerun the
|
|
522
|
+
same command, and paste both runs. Green only after the clean rerun counts;
|
|
523
|
+
until then the task stays `in_progress` and nothing is committed or closed on
|
|
524
|
+
the explanation.
|
|
509
525
|
- **Two fixes, one premise → stop.** When two fixes resting on the same assumption have
|
|
510
526
|
failed the same check, write that assumption in one sentence and make no third edit on
|
|
511
527
|
it. Test the assumption itself first (print the value, query the real dependency, run
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
// Forked into a newly owned group; ordinary module imports do not start work.
|
|
3
3
|
import { fork, spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { isMainModule } from '../../../scripts/lib/is-main.mjs';
|
|
5
6
|
const entry = fileURLToPath(import.meta.url);
|
|
6
7
|
export function spawnBdRead(binary, { readDeadlineMs = 20250, commandArgs, ...options }) {
|
|
7
8
|
const args = [binary, String(readDeadlineMs)];
|
|
8
9
|
if (commandArgs !== undefined) args.push(JSON.stringify(commandArgs));
|
|
9
10
|
return fork(entry, args, { ...options, silent: true });
|
|
10
11
|
}
|
|
11
|
-
if (
|
|
12
|
+
if (isMainModule(import.meta.url)) {
|
|
12
13
|
const grouped = process.platform !== 'win32';
|
|
13
14
|
let child;
|
|
14
15
|
function killOwnedGroup() {
|
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { treeReceipt, latestApproval, compareReceipts, mergeBase } from '../../../scripts/lib/receipt.mjs';
|
|
6
6
|
import { secondOpinionForTree } from '../../../scripts/lib/second-opinion-for-tree.mjs';
|
|
7
7
|
import { affectedTests } from '../../../scripts/lib/affected-tests.mjs';
|
|
8
|
+
import { isMainModule } from '../../../scripts/lib/is-main.mjs';
|
|
8
9
|
const entry = fileURLToPath(import.meta.url);
|
|
9
10
|
export const receiptReadEntry = entry;
|
|
10
11
|
|
|
@@ -20,7 +21,7 @@ function bindingTree() {
|
|
|
20
21
|
if (!receipt && gitMetadata) throw Error('Repository receipt unavailable');
|
|
21
22
|
return receipt;
|
|
22
23
|
}
|
|
23
|
-
if (
|
|
24
|
+
if (isMainModule(import.meta.url)) {
|
|
24
25
|
try {
|
|
25
26
|
const mode = process.argv[2] || 'binding';
|
|
26
27
|
if (!['binding', 'approval', 'second-opinion', 'touches'].includes(mode) || process.argv.length > 3) throw Error('Invalid evidence operation');
|
|
@@ -24,6 +24,7 @@ import { execFileSync } from "node:child_process";
|
|
|
24
24
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
25
25
|
import { join } from "node:path";
|
|
26
26
|
import { adaptiveSpecialistPlan, planNotice } from '../lib/specialist-plan.mjs';
|
|
27
|
+
import { isMainModule } from "../lib/is-main.mjs";
|
|
27
28
|
|
|
28
29
|
const cwd = process.cwd();
|
|
29
30
|
const greatCtoDir = join(cwd, ".great_cto");
|
|
@@ -444,6 +445,5 @@ function main() {
|
|
|
444
445
|
}
|
|
445
446
|
|
|
446
447
|
// Guarded so reviewer-nudge.mjs can import RULES/shouldExclude without running the scan.
|
|
447
|
-
|
|
448
|
-
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
448
|
+
const isMain = isMainModule(import.meta.url);
|
|
449
449
|
if (isMain) main();
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import { spawnSync } from 'node:child_process';
|
|
25
25
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
26
26
|
import { join, dirname, basename, extname, relative, sep } from 'node:path';
|
|
27
|
-
import {
|
|
27
|
+
import { isMainModule } from './is-main.mjs';
|
|
28
28
|
|
|
29
29
|
// Dependency, build and tool-state directories: they hold no test of the project's
|
|
30
30
|
// own code, and walking them (node_modules alone can be 100k files) is the slow part.
|
|
@@ -115,7 +115,7 @@ export function affectedTests({ cwd = process.cwd(), changed } = {}) {
|
|
|
115
115
|
return { mode: commands.length ? 'targeted' : 'full', commands, tests, unmapped };
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
const isMain =
|
|
118
|
+
const isMain = isMainModule(import.meta.url);
|
|
119
119
|
if (isMain) {
|
|
120
120
|
const a = process.argv.slice(2);
|
|
121
121
|
const i = a.indexOf('--cwd');
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import { isGlobalLayer, isProjectState } from './great-cto-scope.mjs';
|
|
28
28
|
import { appendFileSync, closeSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, statSync } from 'node:fs';
|
|
29
29
|
import { join } from 'node:path';
|
|
30
|
+
import { isMainModule } from './is-main.mjs';
|
|
30
31
|
|
|
31
32
|
export const EVENTS_FILE = 'events.jsonl';
|
|
32
33
|
// `hint`: a context hook (edit-impact, lesson-tripwire) put text in front of the model.
|
|
@@ -195,7 +196,7 @@ export function readEventsSince(dir, cursor, { limit = 50, maxDelta = MAX_DELTA
|
|
|
195
196
|
return { state: 'live', mode: 'delta', events, cursor: next, bad };
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
if (import.meta.url
|
|
199
|
+
if (isMainModule(import.meta.url)) {
|
|
199
200
|
try {
|
|
200
201
|
const argv = process.argv.slice(2);
|
|
201
202
|
const arg = (name) => { const i = argv.indexOf(name); return i >= 0 ? argv[i + 1] : undefined; };
|
|
@@ -25,6 +25,7 @@ import os from 'node:os';
|
|
|
25
25
|
import path from 'node:path';
|
|
26
26
|
import readline from 'node:readline';
|
|
27
27
|
import { createHash } from 'node:crypto';
|
|
28
|
+
import { isMainModule } from './is-main.mjs';
|
|
28
29
|
|
|
29
30
|
const INDEX_VERSION = 1;
|
|
30
31
|
|
|
@@ -154,7 +155,7 @@ export async function agentUsage({ logsDir = DEFAULT_LOGS_DIR, cacheFile = DEFAU
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
// CLI: node scripts/lib/agent-usage.mjs [--json]
|
|
157
|
-
const isMain =
|
|
158
|
+
const isMain = isMainModule(import.meta.url);
|
|
158
159
|
if (isMain) {
|
|
159
160
|
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
|
|
160
161
|
const agents = fs.readdirSync(path.join(root, 'agents')).filter((f) => f.endsWith('.md')).map((f) => f.slice(0, -3));
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
// agent's prompt, re-derived each time; one testable definition means the agents
|
|
19
19
|
// agree by construction.
|
|
20
20
|
|
|
21
|
+
import { isMainModule } from './is-main.mjs';
|
|
22
|
+
|
|
21
23
|
/** Gate sets per level, in pipeline order. */
|
|
22
24
|
const LEVEL_GATES = Object.freeze({
|
|
23
25
|
auto: [],
|
|
@@ -162,7 +164,7 @@ export function describeLevel(level, opts) {
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
// ── CLI ─────────────────────────────────────────────────────────────────────
|
|
165
|
-
const isMain = import.meta.url
|
|
167
|
+
const isMain = isMainModule(import.meta.url);
|
|
166
168
|
if (isMain) {
|
|
167
169
|
const argv = process.argv.slice(2);
|
|
168
170
|
const level = argv.find((a) => !a.startsWith('--')) || DEFAULT_LEVEL;
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
* → one JSON line: { cursor, changes: [...], timedOut }
|
|
25
25
|
* With no --since it answers at once with the current cursor and nothing else.
|
|
26
26
|
*/
|
|
27
|
-
import { fileURLToPath } from 'node:url';
|
|
28
27
|
import { latestVerdicts } from './ship-evidence.mjs';
|
|
29
28
|
import { readSessionStatus } from './session-status.mjs';
|
|
29
|
+
import { isMainModule } from './is-main.mjs';
|
|
30
30
|
|
|
31
31
|
export const MAX_TIMEOUT_S = 600;
|
|
32
32
|
|
|
@@ -82,7 +82,7 @@ export async function waitForChange({ cwd = process.cwd(), since = null, timeout
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
const isMain =
|
|
85
|
+
const isMain = isMainModule(import.meta.url);
|
|
86
86
|
if (isMain) {
|
|
87
87
|
const a = process.argv.slice(2);
|
|
88
88
|
const opt = (k, d) => { const i = a.indexOf(k); return i >= 0 ? a[i + 1] : d; };
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
// Plan: docs/plans/PLAN-2026-06-18-gate-tiering-reviewer-consolidation.md
|
|
14
14
|
|
|
15
|
+
import { isMainModule } from './is-main.mjs';
|
|
15
16
|
export const TIER_RANK = { T0: 0, T1: 1, T2: 2 };
|
|
16
17
|
|
|
17
18
|
// Files that, on their own, never change external behavior. A change touching ONLY
|
|
@@ -113,7 +114,7 @@ export function classify(signals = {}) {
|
|
|
113
114
|
|
|
114
115
|
// CLI: node scripts/lib/change-tier.mjs file1 file2 … → prints the tier + reasons
|
|
115
116
|
// (a thin probe; the orchestrator imports classify() directly with richer signals).
|
|
116
|
-
if (import.meta.url
|
|
117
|
+
if (isMainModule(import.meta.url)) {
|
|
117
118
|
const r = classify({ changedFiles: process.argv.slice(2) });
|
|
118
119
|
process.stdout.write(`${r.tier} ${r.reasons.join(', ')}\n`);
|
|
119
120
|
}
|