@walwal-harness/cli 7.1.55 → 7.1.57
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/CHANGELOG.md +17 -0
- package/HR-Resource/cdo/SKILL.md +6 -0
- package/HR-Resource/ceo/SKILL.md +3 -0
- package/HR-Resource/coo/SKILL.md +6 -0
- package/HR-Resource/cqo/SKILL.md +18 -2
- package/HR-Resource/cto/SKILL.md +28 -0
- package/HR-Resource/ops/SKILL.md +15 -0
- package/assets/templates/AGENTS.md.template +3 -3
- package/conventions/README.md +2 -0
- package/conventions/shared.md +10 -0
- package/gotchas/README.md +2 -0
- package/gotchas/cqo.md +11 -0
- package/gotchas/cto.md +7 -0
- package/gotchas/ops.md +6 -0
- package/gotchas/shared.md +16 -0
- package/package.json +1 -1
- package/scripts/harness-company-complete.sh +57 -1
- package/scripts/harness-corpus-reachability.sh +128 -0
- package/scripts/harness-gotcha-register.sh +20 -5
- package/scripts/harness-spec-pin.sh +91 -0
- package/scripts/harness-worker-evidence-validate.sh +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,23 @@ docmeta:
|
|
|
31
31
|
|
|
32
32
|
## Unreleased
|
|
33
33
|
|
|
34
|
+
## 7.1.57 — Reading is not reaching: corpus reachability, spec pins, the document is the record (2026-09-01)
|
|
35
|
+
Applies the field-trial revision of the change proposal. Its P1/P4/P5/P6/P9 shipped in 7.1.55–56 and the trial marks them **field-tested** — briefed verbatim into two agents that were not told they were observed, scored on instruments fixed in advance: correct ordering, 4 real citations, 0 fabrications, and a published not-read list. **Three proposals are new, and one claim was corrected.**
|
|
36
|
+
|
|
37
|
+
- **Reachability (new, the trial's own discovery).** The trial's agent followed the reading rule exactly and still missed the entry written for its exact bug — because that entry named `cto` in its own front matter and was linked from `cqo`'s index alone. Lazy loading tells a reader to consult `{shared, own-role}`; that is a **promise about reachability**, and where it is not kept the rule does not narrow the search, **it hides the entry**. Measured: 69 items, **10 unreachable role-routings, 7 invisible to a role the entry itself named** — every one written by a single role and filed only under that role's index. New `scripts/harness-corpus-reachability.sh` audits and (`--fix`) repairs this; `harness-gotcha-register.sh` gains `--roles` and auto-links at registration, so an entry cannot be filed under its author alone. **P1 makes agents read the index; this is what makes the index worth reading.**
|
|
38
|
+
- **Spec pins (new).** `scripts/harness-spec-pin.sh` records the version **and content hash** of every external spec a mission builds against in `{mission}/spec-pins.json`, and re-verifies before completion and archive. This was the trial's actual defect: a spec moved `v0.7 → v0.9`, changing a response contract, while the category built against `v0.7` sat marked **complete** — two revisions had landed silently. The symptom was not an error but an absence: a lookup key stopped matching and three overlays were dropped as `null`. **A category is complete against a spec version, never in the abstract.**
|
|
39
|
+
- **The document is the record (new).** A conclusion a session holds but has not written into its role document and the runtime state file is not held by the company; reconcile before reporting, strike and correct in place, never delete, and check role documents against peer documents. `harness-worker-evidence-validate.sh` now catches the measured case mechanically — a worker whose report says `COMPLETE` while `progress.json` still lists it running. Cost of the unreconciled version: an orchestration loop that went on trying to spawn a finished step **70 times**.
|
|
40
|
+
- **Instrument validity gains a statistics clause.** A summary statistic is published **with its `n`**, and a spiky series is characterised by **percentiles, never min–max** — a range is the two least representative points in the set and reads as a finding.
|
|
41
|
+
- **Zero new numbered rules.** The proposal's own closing question was whether nine more entries on a nineteen-item list is P1's failure mode wearing the shape of a fix. §8 stays at 22: reachability folds into Rule 11 (which becomes *lazy loading is a promise about reachability*), spec pins into Rule 4 (*nothing is complete in the abstract*), and the record clause into Rule 12 (now *the document is the record*). The enforcement lives in scripts, not in more prose.
|
|
42
|
+
- Gate ordering fixed while wiring this: the spec-pin check initially sat after the runtime transition, where a refusal would have refused nothing. All three completion gates now run **before** `progress.json` is touched, verified by asserting the runtime stays `running` on refusal.
|
|
43
|
+
- The audit found two unreachable routings in this package's own bundled corpus on first run, and read its own syntax documentation as data until the declaration was anchored to line start. Both fixed; the bundled corpus now audits clean at install.
|
|
44
|
+
- On P9, the trial **corrected** the earlier claim rather than confirming it: the same verbatim rule was obeyed by the second agent, so the honest figure is one of two — not "prose does not work" but "prose works at a rate you cannot predict per agent." Still the argument for a hook, since a gate has no compliance rate, but the earlier framing overstated it.
|
|
45
|
+
|
|
46
|
+
## 7.1.56 — Close the lessons-gate bypass at the terminal transition (2026-09-01)
|
|
47
|
+
- **Fix: the Hard Rule 20 gate could be bypassed by firing the completion transition.** Found by an A/B sandbox test of 7.1.54 vs 7.1.55 running identical fixtures. 7.1.55 put the gate in `harness-stop.sh` only, but `harness-company-complete.sh` sets `conductor.state=completed`, and `harness-stop.sh` short-circuits on that state at the top of the file — so a mission could complete having never recorded what it read, simply by running the transition. That is precisely the failure the gate was written to prevent. `harness-company-complete.sh` now runs `harness-lessons-gate.sh` itself and refuses the transition (non-zero exit, runtime left in `running`, refusal logged to `progress.log`) when the active mission's role documents lack `## Lessons Preflight` / `## Lessons Tally`.
|
|
48
|
+
- Deadlock-safe by construction, verified against every legitimate stop path: the gate is scoped to the latest **active** mission, so the Stop-hook auto-complete backstop — which fires only when no mission is active — always passes; and `harness-company-block.sh` (external-authority BLOCKED) is deliberately **not** gated, since a mission blocked on a missing credential must still be able to stop.
|
|
49
|
+
- Two escape hatches: `HARNESS_SKIP_LESSONS_GATE=1` for a single call, and `behavior.lessons_gate:false` for a project that has not adopted the sections.
|
|
50
|
+
|
|
34
51
|
## 7.1.55 — Read before plan: lessons-before-planning ordering + Stop-hook gate (2026-09-01)
|
|
35
52
|
- **Hard Rule 20 — Lessons precede planning.** `AGENTS.md` now carries an *ordering* rule, not another content artifact: before any source edit, any measurement, and before any brief is issued, every CXX and every hired worker reads `.harness/conventions/{shared,role}.md` + `.harness/gotchas/{shared,role}.md`, follows only the topic links those files name, and writes `## Lessons Preflight` (which items apply and why). Every `ceo.md`, `{cxx}.md`, and worker report closes with a one-line `## Lessons Tally` immediately above `## Implementation Notes`; **`0 fired` is valid and must be stated, not omitted** — a tally that only ever reports hits trains agents to manufacture them. The rule also states the propagation clause: a requirement placed on a CXX that its workers must also satisfy is inserted **verbatim** into the worker brief, because *a rule stated one layer above the layer that executes it does not apply*. Origin: a 24h autonomous run in which the company kept re-learning lessons it had already written down — index compliance was already perfect (40 gotchas, 0 orphans, all linked), and the knowledge still arrived after the mistake. **Deliberately not shipped: the distilled 28-item preflight file** that run also produced — a derived corpus must be re-synced whenever any source file changes, went factually stale within an hour, and would be a second thing nobody reads before planning.
|
|
36
53
|
- **Hard Rule 21 — every worker spawn declares its model.** Never inherit the CLI default. The model is recorded in the brief, the Worker Evidence Manifest, and `progress.json company_state.workers[]`. A worker terminated by a usage limit is indistinguishable, from the outside, from a worker that finished, so **a silent or truncated worker is a rate limit until proven otherwise**. `scripts/harness-worker-dispatch.sh` now resolves an explicit model (`company_mode.worker_model` → `flow.team.{gen,eval}_model` → `opus`), passes `--model` on every `claude -p` spawn form, and records it in the queue, the dispatch record, and `progress.log`.
|
package/HR-Resource/cdo/SKILL.md
CHANGED
|
@@ -81,6 +81,12 @@ Required output sections:
|
|
|
81
81
|
|
|
82
82
|
Every CDO worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/cdo/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
|
|
83
83
|
|
|
84
|
+
## The Document Is The Record
|
|
85
|
+
|
|
86
|
+
A conclusion you hold but have not written into `cdo.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
|
|
87
|
+
|
|
88
|
+
Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
|
|
89
|
+
|
|
84
90
|
## Worker Spawn Contract
|
|
85
91
|
|
|
86
92
|
Two things are decided **before** the round starts, not after a worker dies.
|
package/HR-Resource/ceo/SKILL.md
CHANGED
|
@@ -113,6 +113,9 @@ The read is an **ordering constraint**, not a reading list. It happens before th
|
|
|
113
113
|
- CEO must not accept CQO PASS for a runnable product unless OPS has supplied clean verification-watch evidence or an explicit not-applicable reason. Open OPS incidents, missing runtime mapping, missing required logs, service down, or health mismatch block Owner acceptance.
|
|
114
114
|
- After launch, CEO treats OPS production incidents as company events. CEO convenes CTO/CQO/OPS when user-impacting production signals appear; CTO owns recovery, CQO owns regression confirmation, and OPS owns evidence and close criteria.
|
|
115
115
|
- Every CEO and CXX mission document must include an English `## Implementation Notes` section with the required subsections below. CEO must reject CXX reports that omit it.
|
|
116
|
+
- A conclusion a CXX holds but has not written into its `{cxx}.md` **and** into `progress.json` is not held by the company. Before accepting any reported state change, CEO checks that it is reconciled in both; a report that contradicts its own document, or a peer's, is returned. Strike and correct in place — never delete a superseded line.
|
|
117
|
+
- Missions that build against an external spec carry `{mission}/spec-pins.json` (version + content hash). CEO requires CTO to pin before implementation and CQO to verify before PASS. Nothing is complete in the abstract.
|
|
118
|
+
- A registered convention/gotcha is linked from the index of **every role it names as an audience**. CEO treats an unreachable entry as an unregistered one.
|
|
116
119
|
- Every CEO and CXX mission document and every worker report must carry `## Lessons Preflight` and a one-line `## Lessons Tally` (tally immediately before `## Implementation Notes`). `0 fired` is a valid tally; an omitted tally is not. CEO must reject reports that omit either section.
|
|
117
120
|
- Every worker spawn declares its model explicitly — never the inherited CLI default. CEO requires the model in each CXX's Worker Task Briefs and Worker Evidence Manifest. **A silent or truncated worker is a rate limit until proven otherwise**: before treating a stalled round as a CXX failure, CEO asks for the usage-limit status and reset time, because a rate-limited worker looks exactly like a finished one from the outside.
|
|
118
121
|
- Every worker report file is created **before the worker starts**, already carrying its required sections (seeded from `.harness/shared/templates/worker-report.md`). A worker killed mid-round must leave a valid partial report, never a stub. CEO treats a stub report as a seeding failure by the owning CXX, not as a worker failure.
|
package/HR-Resource/coo/SKILL.md
CHANGED
|
@@ -94,6 +94,12 @@ Required output sections:
|
|
|
94
94
|
|
|
95
95
|
Every COO worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/coo/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
|
|
96
96
|
|
|
97
|
+
## The Document Is The Record
|
|
98
|
+
|
|
99
|
+
A conclusion you hold but have not written into `coo.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
|
|
100
|
+
|
|
101
|
+
Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
|
|
102
|
+
|
|
97
103
|
## Worker Spawn Contract
|
|
98
104
|
|
|
99
105
|
Two things are decided **before** the round starts, not after a worker dies.
|
package/HR-Resource/cqo/SKILL.md
CHANGED
|
@@ -33,7 +33,8 @@ Do not distill the corpus into a private checklist file and read that instead. A
|
|
|
33
33
|
5. Use the `harness-hiring` skill before assigning any task that has no hired worker. Do not complete that task yourself.
|
|
34
34
|
6. Define quality gates and delegate evidence collection to hired workers in fresh sessions.
|
|
35
35
|
7. Monitor repeated issues and promote verified lessons to `.harness/conventions`, `.harness/gotchas`, `.harness/memories`, or `.harness/shared`.
|
|
36
|
-
8.
|
|
36
|
+
8. Run `bash scripts/harness-spec-pin.sh . {goal-or-child-mission} verify` before any PASS and before archive. A category is complete **against a spec version**, never in the abstract; drift means the verified scope no longer matches what the work was built for, and the verdict is BLOCKED until CTO re-checks the affected work and re-pins.
|
|
37
|
+
9. Approve or reject archive based solely on worker-provided evidence and OPS runtime/watch evidence when the mission uses a runnable environment.
|
|
37
38
|
|
|
38
39
|
## Worker Activity Telemetry
|
|
39
40
|
|
|
@@ -64,6 +65,14 @@ If changed-scope tests or changed-scope coverage fail, CQO returns FAIL or BLOCK
|
|
|
64
65
|
|
|
65
66
|
CQO must report any out-of-scope full-suite failure or coverage deficit to CEO and CTO with command output, affected paths, and the classification above. CQO must not expand the mission into broad unrelated test-writing work unless CEO explicitly routes that as a new task.
|
|
66
67
|
|
|
68
|
+
## Reachability, Not Just Reading
|
|
69
|
+
|
|
70
|
+
Lazy loading is a **promise about reachability**. When you register a convention or gotcha, declare every role that should be able to find it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and link it from **each** of those roles' index files, not only your own.
|
|
71
|
+
|
|
72
|
+
**The failure is filing under yourself.** Registration feels complete because the entry is indexed; it just is not where its declared readers are told to look. Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named.** An agent that follows the reading rule exactly still never sees them — the rule stops narrowing the search and starts hiding the entry.
|
|
73
|
+
|
|
74
|
+
Verify with `bash scripts/harness-corpus-reachability.sh . text` (add `--fix` to link what is missing). This runs at the completion gate, so an unreachable corpus blocks the mission from closing.
|
|
75
|
+
|
|
67
76
|
## Instrument Validity
|
|
68
77
|
|
|
69
78
|
Evidence about what did **not** happen is worth exactly as much as the instrument that looked for it.
|
|
@@ -72,6 +81,7 @@ Evidence about what did **not** happen is worth exactly as much as the instrumen
|
|
|
72
81
|
- Report the control next to the result: what was injected, that it was observed, and the negative result from the same run. A verdict resting on unproven negative evidence is **BLOCKED**, not PASS.
|
|
73
82
|
- **Where an instrument is supplied by a dependency rather than written in-repo, its filtering behaviour is read from source and quoted** — package, version, file, line range — not inferred from observed output. A filter that lives upstream is invisible to every in-repo search, so its absence from the project's own code is not evidence of its absence.
|
|
74
83
|
- When an instrument turns out to have been structurally null, the claims it produced are identifiable **by their shape** — every claim of that form, not just the one that happened to be noticed. Re-open them as a class and say so in Recurrence Notes.
|
|
84
|
+
- A summary statistic is published **with its `n`**, and a spiky series is characterised by **percentiles, never min–max** — a range is the two least representative points in the set, and reads as a finding.
|
|
75
85
|
- An audit question that offers alternatives asserts that the alternatives are exhaustive. "Is it A or B?" cannot return "neither, it is upstream". When an audit stalls, re-ask the question without the menu.
|
|
76
86
|
|
|
77
87
|
## Hard Rules
|
|
@@ -103,7 +113,7 @@ Required output sections in `cqo.md`:
|
|
|
103
113
|
4. Instrument Validity — for every negative claim: the instrument, its log level and filter (quoted from source when the instrument comes from a dependency), and the positive control that fired in the same run. Negative evidence with no control is BLOCKED, not PASS.
|
|
104
114
|
5. OPS Watch Evidence — ops report path, monitored runtime mapping, incidents/warnings, and whether runtime evidence permits PASS.
|
|
105
115
|
6. CQO Verdict — PASS, FAIL, or BLOCKED based only on worker evidence plus required OPS watch evidence. Must reference Worker Evidence Manifest entries.
|
|
106
|
-
7. Recurrence Notes — accepted gotchas, conventions, memories, or none.
|
|
116
|
+
7. Recurrence Notes — accepted gotchas, conventions, memories, or none. Every entry registered here names **every role that should be able to find it** and is linked from each of those roles' indexes; `scripts/harness-corpus-reachability.sh` must pass.
|
|
107
117
|
8. Lessons Tally — one line naming which preflight items actually fired. `0 fired` is valid and must be stated.
|
|
108
118
|
9. Implementation Notes — in English, with `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`.
|
|
109
119
|
|
|
@@ -129,6 +139,12 @@ Every CQO evaluator/tester brief must require the worker to append this English
|
|
|
129
139
|
|
|
130
140
|
The worker notes must cover risks, self-corrections, and chosen direction. Use `None` when a subsection has no entries. CQO must not accept evaluator output that omits this block.
|
|
131
141
|
|
|
142
|
+
## The Document Is The Record
|
|
143
|
+
|
|
144
|
+
A conclusion you hold but have not written into `cqo.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
|
|
145
|
+
|
|
146
|
+
Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
|
|
147
|
+
|
|
132
148
|
## Worker Spawn Contract
|
|
133
149
|
|
|
134
150
|
Two things are decided **before** the round starts, not after a worker dies.
|
package/HR-Resource/cto/SKILL.md
CHANGED
|
@@ -57,6 +57,28 @@ Every CTO worker brief that may use Playwright, browser automation, browser-base
|
|
|
57
57
|
|
|
58
58
|
CTO must not accept worker plans or reports that omit this requirement when browser automation is in scope.
|
|
59
59
|
|
|
60
|
+
## Reachability, Not Just Reading
|
|
61
|
+
|
|
62
|
+
Lazy loading is a **promise about reachability**. When you register a convention or gotcha, declare every role that should be able to find it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and link it from **each** of those roles' index files, not only your own.
|
|
63
|
+
|
|
64
|
+
**The failure is filing under yourself.** Registration feels complete because the entry is indexed; it just is not where its declared readers are told to look. Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named.** An agent that follows the reading rule exactly still never sees them — the rule stops narrowing the search and starts hiding the entry.
|
|
65
|
+
|
|
66
|
+
Verify with `bash scripts/harness-corpus-reachability.sh . text` (add `--fix` to link what is missing). This runs at the completion gate, so an unreachable corpus blocks the mission from closing.
|
|
67
|
+
|
|
68
|
+
## Spec Version Pins
|
|
69
|
+
|
|
70
|
+
Nothing is complete in the abstract. **A category is complete against a spec version.**
|
|
71
|
+
|
|
72
|
+
When the mission builds against any external spec — an API contract, a schema, a partner document, a standard — record it before implementation starts:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
bash scripts/harness-spec-pin.sh . {goal-or-child-mission} add <name> <path> <version>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This stores the version **and a content hash** in `{mission}/spec-pins.json`. Re-run `... verify` before handing off to CQO and before declaring any category done. The completion gate re-checks the pins, so drift blocks the mission from closing.
|
|
79
|
+
|
|
80
|
+
Measured: a spec moved `v0.7 → v0.9`, changing a response contract, while the category built against `v0.7` sat marked **complete**. Two later revisions had landed silently. The symptom was not an error — a lookup key stopped matching, and three overlays were dropped as `null`. **No error, no log, just an absence.** Nothing in the harness recorded which version the work had been for.
|
|
81
|
+
|
|
60
82
|
## Test Coverage Scope
|
|
61
83
|
|
|
62
84
|
CTO must optimize engineering verification around the work actually changed in the mission. CTO worker briefs must require targeted tests, coverage checks, and regression commands for the changed files, modules, APIs, flows, and directly affected dependencies only.
|
|
@@ -116,6 +138,12 @@ Every CTO worker brief must require the worker to append this English block to t
|
|
|
116
138
|
|
|
117
139
|
The worker notes must cover risks, self-corrections, and chosen direction. Use `None` when a subsection has no entries. CTO must not accept worker output that omits this block.
|
|
118
140
|
|
|
141
|
+
## The Document Is The Record
|
|
142
|
+
|
|
143
|
+
A conclusion you hold but have not written into `cto.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
|
|
144
|
+
|
|
145
|
+
Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
|
|
146
|
+
|
|
119
147
|
## Worker Spawn Contract
|
|
120
148
|
|
|
121
149
|
Two things are decided **before** the round starts, not after a worker dies.
|
package/HR-Resource/ops/SKILL.md
CHANGED
|
@@ -79,12 +79,21 @@ After service launch, OPS continues the same monitoring duty against `runtime.pr
|
|
|
79
79
|
- Repeated incidents must trigger recovery coordination through CEO -> CTO/CQO/OPS. OPS supplies evidence and recovery criteria; CTO owns fixes; CQO owns regression confirmation.
|
|
80
80
|
- OPS may classify resolved events as close candidates only after the monitored endpoint is healthy and logs no longer show the triggering error pattern.
|
|
81
81
|
|
|
82
|
+
## Reachability, Not Just Reading
|
|
83
|
+
|
|
84
|
+
Lazy loading is a **promise about reachability**. When you register a convention or gotcha, declare every role that should be able to find it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and link it from **each** of those roles' index files, not only your own.
|
|
85
|
+
|
|
86
|
+
**The failure is filing under yourself.** Registration feels complete because the entry is indexed; it just is not where its declared readers are told to look. Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named.** An agent that follows the reading rule exactly still never sees them — the rule stops narrowing the search and starts hiding the entry.
|
|
87
|
+
|
|
88
|
+
Verify with `bash scripts/harness-corpus-reachability.sh . text` (add `--fix` to link what is missing). This runs at the completion gate, so an unreachable corpus blocks the mission from closing.
|
|
89
|
+
|
|
82
90
|
## Instrument Validity
|
|
83
91
|
|
|
84
92
|
OPS supplies most of the harness's negative evidence — "no crash", "no error in the log", "health stayed green" — so OPS owns proving the instrument could have seen the failure.
|
|
85
93
|
|
|
86
94
|
- Every claim of the form "nothing bad happened" ships with a **positive control that fired in the same run** and varied the exact variable under suspicion. Without one, report the observation as unverified, not clean.
|
|
87
95
|
- **Read dependency-supplied filtering from source and quote it** — package, version, file, line range — instead of inferring it from what appeared in the log. Log middleware, dev servers, proxies, and test runners routinely drop successful or sub-threshold requests at a log level nobody chose deliberately, and that rule appears nowhere in the project's own code.
|
|
96
|
+
- A summary statistic is published **with its `n`**, and a spiky series is characterised by **percentiles, never min–max** — a range is the two least representative points in the set, and reads as a finding.
|
|
88
97
|
- Record the instrument in Environment Evidence: what tool observed the runtime, at what log level, with what filter, and what the control was. An unrecorded instrument makes every negative result from that run unusable.
|
|
89
98
|
|
|
90
99
|
## Port Policy
|
|
@@ -143,6 +152,12 @@ OPS must not accept worker plans or reports that omit this requirement when brow
|
|
|
143
152
|
|
|
144
153
|
Every OPS worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/ops/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
|
|
145
154
|
|
|
155
|
+
## The Document Is The Record
|
|
156
|
+
|
|
157
|
+
A conclusion you hold but have not written into `ops.md` **is not held by the company.** Before reporting any state change — to CEO, to a peer CXX, to the Owner — reconcile it in your own document *and* in `progress.json`. Strike and correct in place; never delete the superseded line, because a reader arriving later needs to see that it was superseded rather than never written.
|
|
158
|
+
|
|
159
|
+
Check your document against your peers' documents, not only against itself. The cheap version of this failure is a deliverable table that contradicts three messages you already sent. The expensive version was measured: a completed step reported and accepted, never written to the state file, and an orchestration loop that went on trying to spawn it **70 times**.
|
|
160
|
+
|
|
146
161
|
## Worker Spawn Contract
|
|
147
162
|
|
|
148
163
|
Two things are decided **before** the round starts, not after a worker dies.
|
|
@@ -169,15 +169,15 @@ All Playwright usage by CEO, CXX, and hired workers must run with a visible real
|
|
|
169
169
|
1. **No source edit without `{mission}/cto.md`** — CTO scope sign-off is required before any source file is modified.
|
|
170
170
|
2. **No CXX impersonation** — The active model must not act as CEO/CTO/CQO inline. Use installed harness skills in fresh sessions.
|
|
171
171
|
3. **No unnamed workers** — All specialist work routes through `harness-hiring` → `harness-resource-manager`.
|
|
172
|
-
4. **No archive without CQO verdict** — `{mission}/cqo.md` with explicit PASS must exist.
|
|
172
|
+
4. **No archive without CQO verdict, and nothing is complete in the abstract** — `{mission}/cqo.md` with explicit PASS must exist. **A mission records the version and content hash of every external spec it builds against** (`{mission}/spec-pins.json`, managed by `scripts/harness-spec-pin.sh`), and the pins are re-checked against the current documents **before any category is marked complete and before any archive**. A category is complete *against a spec version*, never in the abstract: a spec can move under a category already marked done, changing a contract, with every later revision landing silently.
|
|
173
173
|
5. **No gotcha skip** — Every hot-fix produces at least one `.harness/gotchas/` or `.harness/conventions/` entry.
|
|
174
174
|
6. **This file is read-only during missions** — Raise a separate `/goal` to update AGENTS.md.
|
|
175
175
|
7. **CEO routes only to CXX — never to workers** — CEO must not dispatch, hire, or brief specialist workers directly. Implementation workers are hired by CTO. Evaluator/tester workers are hired by CQO.
|
|
176
176
|
8. **No CXX self-execution** — CXX agents coordinate and manage only. A CXX that produces deliverables without matching worker records has violated its scope. CEO must reject such reports.
|
|
177
177
|
9. **No verdict without worker evidence** — CQO cannot issue ACCEPTED/REJECTED without a Worker Evidence Manifest referencing at least one evaluator worker. Self-inspection by CQO is not valid evidence.
|
|
178
178
|
10. **Hierarchical worker ownership** — Hired workers are installed under `.claude/skills/{owning-cxx}/{worker}/` and `.codex/skills/{owning-cxx}/{worker}/`; mission worker reports live under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/`. Flat `{mission}/workers/` reports are legacy and signal an ownership violation unless explicitly migrated.
|
|
179
|
-
11. **Lazy convention/gotcha loading** — *What* to read. *When* to read it, and what to write about it, is Rule 20. CXX roles read `.harness/conventions/shared.md`, `.harness/conventions/{cxx}.md`, `.harness/gotchas/shared.md`, and `.harness/gotchas/{cxx}.md`, then follow only the related topic links listed in those CXX files. Workers read only the related links supplied by their owning CXX.
|
|
180
|
-
12. **
|
|
179
|
+
11. **Lazy convention/gotcha loading is a promise about reachability** — *What* to read. *When* to read it, and what to write about it, is Rule 20. CXX roles read `.harness/conventions/shared.md`, `.harness/conventions/{cxx}.md`, `.harness/gotchas/shared.md`, and `.harness/gotchas/{cxx}.md`, then follow only the related topic links listed in those CXX files. Workers read only the related links supplied by their owning CXX. **An entry is linked from the index of every role it names as an audience, not only its author's.** Registration is incomplete until each declared audience can reach it by the path that role is told to use — where the promise is not kept, this rule does not narrow the search, **it hides the entry**. Declare the audience (`<!-- roles: cto, cqo -->` on a topic file, `- **Roles**: cto, cqo` on an index entry) and verify with `scripts/harness-corpus-reachability.sh`. The recurring failure is mundane: *the author files under itself*, and registration feels complete because the entry is indexed — just not where its declared readers look.
|
|
180
|
+
12. **The document is the record** — A conclusion a session holds but has not written into its role document **is not held by the company**. Before reporting a state change to anyone, reconcile it in your own document and in the runtime state file; strike and correct in place, never delete. Role documents are checked against peer documents, not only against themselves. `ceo.md`, every `{cxx}.md`, and every worker report must end with one English `## Implementation Notes` section containing `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`. Use `None` for empty subsections. Do not create a separate sidecar notes file; the notes belong at the bottom of the same role or worker report that produced the decision/evidence.
|
|
181
181
|
13. **Structured runtime state** — If the harness must parse it, record it in JSON or JSONL. CXX todo queues, event history, heartbeat timestamps, preemption/resume state, and completion evidence belong in `.harness/todos/*.json*` or `.harness/events.jsonl`, not in free-form Markdown tables. Markdown remains for instructions, conventions, gotchas, skills, and human-readable mission narrative.
|
|
182
182
|
14. **Mission lifecycle is explicit** — Every goal, submission, and hot-fix directory must contain `mission-state.json` with `lifecycle` and `active`. Only one child mission under a goal may be active. Starting a newer submission/hot-fix closes, cancels, or supersedes the previous active child unless CEO records a deliberate TODO/resume plan.
|
|
183
183
|
15. **Owner is final acceptance only** — Owner is not a tester or QA substitute. CEO/CXX must not report "done, please check" until worker-backed verification proves the goal can be completed. Use unit tests, E2E, Playwright, test accounts, seeded data, build/run checks, logs, and CQO evidence before requesting Owner acceptance.
|
package/conventions/README.md
CHANGED
|
@@ -18,3 +18,5 @@ Only company-level roles are provided by default:
|
|
|
18
18
|
Topic-specific convention files may use descriptive names such as `i18n-locale.md`.
|
|
19
19
|
|
|
20
20
|
CXX files such as `cto.md` and `cqo.md` act as lazy-loading indexes. Add links there when a topic file applies to that CXX.
|
|
21
|
+
|
|
22
|
+
**Declare the audience.** Every convention names each role that must be able to *find* it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and is linked from each of those roles' index files, not only its author's. Lazy loading is a promise about reachability: an entry filed only under its author is indexed but invisible to the readers it names. Verify with `bash scripts/harness-corpus-reachability.sh . text` (`--fix` adds the missing links). Entries in `shared.md` need no cross-linking; every role reads it.
|
package/conventions/shared.md
CHANGED
|
@@ -24,6 +24,16 @@
|
|
|
24
24
|
- CXX decisions use `{cxx}.md`.
|
|
25
25
|
- Worker reports use `{owning-cxx}/workers/{worker-name}.md`.
|
|
26
26
|
|
|
27
|
+
## Declared Audience
|
|
28
|
+
|
|
29
|
+
Every convention and gotcha declares which roles must be able to **find** it, and is linked from each of those roles' index files.
|
|
30
|
+
|
|
31
|
+
- Topic file: `<!-- roles: cto, cqo -->` near the top.
|
|
32
|
+
- Entry inside an index file: `- **Roles**: cto, cqo`.
|
|
33
|
+
- Registration is complete only when `bash scripts/harness-corpus-reachability.sh . text` passes. `--fix` adds the missing links.
|
|
34
|
+
|
|
35
|
+
Lazy loading tells a reader to consult `{shared, own-role}` and nothing else. That is a **promise about reachability**: where the promise is not kept, the rule stops narrowing the search and starts hiding the entry. An agent following the reading rule exactly will never see an item filed only under someone else's index.
|
|
36
|
+
|
|
27
37
|
## Section-Scoped Reading
|
|
28
38
|
|
|
29
39
|
Any reader that scans a role document, worker report, or mission record for sections — a script, a hook, an agent following a protocol — matches:
|
package/gotchas/README.md
CHANGED
|
@@ -18,3 +18,5 @@ Only company-level roles are provided by default:
|
|
|
18
18
|
Topic-specific gotcha files may use descriptive names such as `i18n-locale-hotfix.md`.
|
|
19
19
|
|
|
20
20
|
CXX files such as `cto.md` and `cqo.md` act as lazy-loading indexes. Add links there when a topic file applies to that CXX.
|
|
21
|
+
|
|
22
|
+
**Declare the audience.** Every gotcha names each role that must be able to *find* it — `<!-- roles: cto, cqo -->` at the top of a topic file, or `- **Roles**: cto, cqo` inside an index entry — and is linked from each of those roles' index files, not only its author's. Lazy loading is a promise about reachability: an entry filed only under its author is indexed but invisible to the readers it names. Verify with `bash scripts/harness-corpus-reachability.sh . text` (`--fix` adds the missing links). Entries in `shared.md` need no cross-linking; every role reads it.
|
package/gotchas/cqo.md
CHANGED
|
@@ -19,3 +19,14 @@ An instrument's filtering behaviour is read from source and quoted — package,
|
|
|
19
19
|
## Audit Questions That Offer Alternatives
|
|
20
20
|
|
|
21
21
|
An audit question that offers alternatives asserts that the alternatives are exhaustive. "Is it a skip rule **or** a status-conditional format?" cannot return "neither, it is upstream" — both branches locate the rule inside our own code, so the true answer is unreachable from the question's grammar. When an audit stalls, re-ask the question without the menu.
|
|
22
|
+
|
|
23
|
+
## A Range Is The Two Least Representative Points
|
|
24
|
+
<!-- roles: cqo, ops -->
|
|
25
|
+
|
|
26
|
+
Publish a summary statistic **with its `n`**, and characterise a spiky series by **percentiles, never min–max**. A range reports the two most extreme observations in the set and reads as a finding; on a spiky series it is almost always noise wearing the shape of a result.
|
|
27
|
+
|
|
28
|
+
## Cross-role Links
|
|
29
|
+
|
|
30
|
+
Entries written under another role that name CQO as an audience.
|
|
31
|
+
|
|
32
|
+
- [Complete Against A Spec Version, Never In The Abstract](./cto.md) — verify `spec-pins.json` before PASS and before archive.
|
package/gotchas/cto.md
CHANGED
|
@@ -11,3 +11,10 @@ Do not start implementation before domain, API, platform, account, and integrati
|
|
|
11
11
|
## Stub Report From A Worker Killed Mid-Round
|
|
12
12
|
|
|
13
13
|
A report assembled at the end of a round becomes a stub when the round is cut short, and a stub halts the company. Create the worker report with every required section present **before** the worker starts, and require it to be filled in incrementally. Same failure, opposite outcome: an unseeded worker killed mid-round leaves a stub and costs a re-run; a seeded worker killed by the same limit leaves an intact partial report and costs nothing. The variable is a decision taken before the round.
|
|
14
|
+
|
|
15
|
+
## Complete Against A Spec Version, Never In The Abstract
|
|
16
|
+
<!-- roles: cto, cqo -->
|
|
17
|
+
|
|
18
|
+
A category marked complete records *what it was complete against*. A spec moved `v0.7 → v0.9`, changing a response contract, while the category built against `v0.7` sat marked done — two later revisions landed silently and nothing in the harness recorded which version the work had been for.
|
|
19
|
+
|
|
20
|
+
The symptom was not an error. A lookup key stopped matching, and three map overlays were dropped as `null`: no exception, no log, just an absence. Pin version **and content hash** with `scripts/harness-spec-pin.sh` before implementation, and re-verify before completion and archive.
|
package/gotchas/ops.md
CHANGED
|
@@ -19,3 +19,9 @@ Do not let CXX agents choose arbitrary ports. CEO must set `HARNESS_BASE_PORT` a
|
|
|
19
19
|
## Clean Log From An Unproven Instrument
|
|
20
20
|
|
|
21
21
|
"Nothing bad appeared in the log" is worth nothing until a positive control proves the log could have shown it. Dev servers, log middleware, proxies, and test runners routinely drop successful or sub-threshold requests at a log level nobody chose deliberately, and that rule appears nowhere in the project's own code. Record the instrument — tool, log level, filter, control — in Environment Evidence, or report the observation as unverified rather than clean.
|
|
22
|
+
|
|
23
|
+
## Cross-role Links
|
|
24
|
+
|
|
25
|
+
Entries written under another role that name OPS as an audience.
|
|
26
|
+
|
|
27
|
+
- [A Range Is The Two Least Representative Points](./cqo.md) — publish `n`, use percentiles, not min–max.
|
package/gotchas/shared.md
CHANGED
|
@@ -29,3 +29,19 @@ A reader anchored on `^#` misses the same heading written as `> ## …`, which i
|
|
|
29
29
|
## Rules Stated One Layer Above The Executing Layer
|
|
30
30
|
|
|
31
31
|
A requirement placed on a CXX that its workers must also satisfy does not reach the workers unless it is inserted **verbatim** into the worker brief. A rule stated one layer above the layer that executes it does not apply, and the layer below cannot infer a rule it was never given.
|
|
32
|
+
|
|
33
|
+
## The Author Files Under Itself
|
|
34
|
+
<!-- roles: ceo, coo, cdo, cto, cqo, ops -->
|
|
35
|
+
|
|
36
|
+
The recurring way an indexed entry becomes unreachable: whoever wrote it filed it under their own role index and nowhere else. Registration *feels* complete — the entry exists, it is indexed, it is linked. It is just not where its declared readers are told to look.
|
|
37
|
+
|
|
38
|
+
Measured on a live corpus: 69 items, **10 unreachable role-routings, 7 of them invisible to a role the entry itself named**. The clearest case was an entry whose own text called two others "the same family" — both siblings were already reachable from the index it was missing from. An inconsistency, not a decision.
|
|
39
|
+
|
|
40
|
+
Declare the audience at registration and let `scripts/harness-corpus-reachability.sh` link it. Reading is not reaching.
|
|
41
|
+
|
|
42
|
+
## A Conclusion Not Written Is Not Held
|
|
43
|
+
<!-- roles: ceo, coo, cdo, cto, cqo, ops -->
|
|
44
|
+
|
|
45
|
+
A conclusion a session holds but has not written into its role document and the runtime state file is not held by the company. Reconcile before reporting; strike and correct in place, never delete.
|
|
46
|
+
|
|
47
|
+
The cheap version: a deliverable table that contradicts three messages already sent, and a line still requesting work a peer has already delivered. The expensive version was measured — a required step completed, reported, and accepted, that never reached the state file, so the orchestration loop went on trying to spawn the finished step **70 times**. "Write your conclusions down" reads as tidiness until it reads as seventy.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.57",
|
|
4
4
|
"description": "Company-style AI agent harness for Claude and Codex. Installs commands, CXX agents, skills, HR-Resource hiring pool, and project-local .harness runtime state.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -16,6 +16,7 @@ REASON="${2:-mission-complete}"
|
|
|
16
16
|
|
|
17
17
|
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
18
18
|
TODOS="$PROJECT_ROOT/.harness/todos/state.json"
|
|
19
|
+
DOCS="$PROJECT_ROOT/.harness/documents"
|
|
19
20
|
[ -f "$PROGRESS" ] || {
|
|
20
21
|
echo "[company-complete] not found: $PROGRESS" >&2
|
|
21
22
|
exit 1
|
|
@@ -25,6 +26,44 @@ command -v jq >/dev/null 2>&1 || {
|
|
|
25
26
|
exit 1
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
# Lessons-before-plan gate (AGENTS.md Hard Rule 20). The Stop hook cannot see
|
|
30
|
+
# this path: once this script sets conductor.state=completed, harness-stop.sh
|
|
31
|
+
# short-circuits at the top, so a mission could complete having never read the
|
|
32
|
+
# corpus simply by firing the transition. Refuse the terminal transition here —
|
|
33
|
+
# this is the transition P6 names, and it is the last point at which refusing
|
|
34
|
+
# still means anything.
|
|
35
|
+
#
|
|
36
|
+
# Safe against deadlock: the gate is scoped to the latest ACTIVE mission, so the
|
|
37
|
+
# stop-hook backstop (which fires only when no mission is active) always passes.
|
|
38
|
+
# Opt out per project with .harness/config.json behavior.lessons_gate=false, or
|
|
39
|
+
# override a single call with HARNESS_SKIP_LESSONS_GATE=1.
|
|
40
|
+
if [ "${HARNESS_SKIP_LESSONS_GATE:-0}" != "1" ] && [ -x "$SCRIPT_DIR/harness-lessons-gate.sh" ]; then
|
|
41
|
+
if ! gate_out="$(bash "$SCRIPT_DIR/harness-lessons-gate.sh" "$PROJECT_ROOT" text latest-active 2>/dev/null)"; then
|
|
42
|
+
{
|
|
43
|
+
echo "[company-complete] REFUSED: the active mission has not recorded what it read before planning."
|
|
44
|
+
echo "$gate_out"
|
|
45
|
+
echo " Add the two sections to each role document, then re-run this transition."
|
|
46
|
+
echo " (override: HARNESS_SKIP_LESSONS_GATE=1, or behavior.lessons_gate=false in .harness/config.json)"
|
|
47
|
+
} >&2
|
|
48
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | company-complete | refused | lessons-gate (Hard Rule 20)" >> "$PROJECT_ROOT/.harness/progress.log" 2>/dev/null || true
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Corpus reachability (Hard Rule 11) and spec pins (Hard Rule 4) are re-checked
|
|
54
|
+
# at the same point, for the same reason: both are promises that decay silently
|
|
55
|
+
# between when they are made and when the mission claims to be done.
|
|
56
|
+
if [ "${HARNESS_SKIP_LESSONS_GATE:-0}" != "1" ] && [ -x "$SCRIPT_DIR/harness-corpus-reachability.sh" ]; then
|
|
57
|
+
if ! reach_out="$(bash "$SCRIPT_DIR/harness-corpus-reachability.sh" "$PROJECT_ROOT" text 2>/dev/null)"; then
|
|
58
|
+
{
|
|
59
|
+
echo "[company-complete] REFUSED: corpus entries are unreachable by roles they name as an audience."
|
|
60
|
+
echo "$reach_out"
|
|
61
|
+
} >&2
|
|
62
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | company-complete | refused | corpus-reachability (Hard Rule 11)" >> "$PROJECT_ROOT/.harness/progress.log" 2>/dev/null || true
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
28
67
|
state_mtime() {
|
|
29
68
|
stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null || echo 0
|
|
30
69
|
}
|
|
@@ -48,6 +87,24 @@ pick_transition_mission_state() {
|
|
|
48
87
|
[ -n "$best" ] && printf '%s\n' "$best"
|
|
49
88
|
}
|
|
50
89
|
|
|
90
|
+
# Spec pins are verified against the mission this transition would close. This
|
|
91
|
+
# sits BEFORE the runtime transition on purpose: a refusal that runs after
|
|
92
|
+
# progress.json is already `completed` has refused nothing.
|
|
93
|
+
if [ "${HARNESS_SKIP_LESSONS_GATE:-0}" != "1" ] && [ -x "$SCRIPT_DIR/harness-spec-pin.sh" ] && [ -d "$DOCS" ]; then
|
|
94
|
+
pin_target="$(pick_transition_mission_state)"
|
|
95
|
+
if [ -n "$pin_target" ]; then
|
|
96
|
+
mission_rel="${pin_target#"$DOCS"/}"; mission_rel="${mission_rel%/mission-state.json}"
|
|
97
|
+
if ! pin_out="$(bash "$SCRIPT_DIR/harness-spec-pin.sh" "$PROJECT_ROOT" "$mission_rel" verify text 2>/dev/null)"; then
|
|
98
|
+
{
|
|
99
|
+
echo "[company-complete] REFUSED: this mission was built against a spec that has since moved."
|
|
100
|
+
echo "$pin_out"
|
|
101
|
+
} >&2
|
|
102
|
+
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | company-complete | refused | spec-pin drift (Hard Rule 4)" >> "$PROJECT_ROOT/.harness/progress.log" 2>/dev/null || true
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
51
108
|
if ! bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
52
109
|
'.company_state.state = "idle" |
|
|
53
110
|
.company_state.active_workers = 0 |
|
|
@@ -85,7 +142,6 @@ if [ -f "$TODOS" ]; then
|
|
|
85
142
|
' "$TODOS" > "$tmp" && mv "$tmp" "$TODOS"
|
|
86
143
|
fi
|
|
87
144
|
|
|
88
|
-
DOCS="$PROJECT_ROOT/.harness/documents"
|
|
89
145
|
if [ -d "$DOCS" ]; then
|
|
90
146
|
target_state="$(pick_transition_mission_state)"
|
|
91
147
|
if [ -n "$target_state" ]; then
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-corpus-reachability.sh — AGENTS.md Hard Rule 11 (reachability clause).
|
|
3
|
+
#
|
|
4
|
+
# Lazy loading tells a reader to consult only {shared, own-role}. That is a
|
|
5
|
+
# PROMISE ABOUT REACHABILITY. Where it is not kept, the rule does not narrow the
|
|
6
|
+
# search — it hides the entry: an item can name a role as its audience, be
|
|
7
|
+
# indexed, and still be invisible to that reader by the path the rule tells it
|
|
8
|
+
# to use. Measured on a live corpus: 69 items, 10 unreachable role-routings,
|
|
9
|
+
# 7 of them invisible to a role the item itself named.
|
|
10
|
+
#
|
|
11
|
+
# The mechanism is mundane and recurs on any install: THE AUTHOR FILES UNDER
|
|
12
|
+
# ITSELF. Registration feels complete because the entry is indexed — just not
|
|
13
|
+
# where its declared readers look.
|
|
14
|
+
#
|
|
15
|
+
# Declaring an audience (either form):
|
|
16
|
+
# file-level (topic files): <!-- roles: cto, cqo -->
|
|
17
|
+
# entry-level (index files): - **Roles**: cto, cqo
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# harness-corpus-reachability.sh <project-root> [text|json] [--fix]
|
|
21
|
+
set -uo pipefail
|
|
22
|
+
|
|
23
|
+
PROJECT_ROOT="${1:-.}"
|
|
24
|
+
MODE="${2:-text}"
|
|
25
|
+
FIX="${3:-}"
|
|
26
|
+
|
|
27
|
+
ROLES="ceo coo cdo cto cqo ops hiring resource-manager brick-office shared"
|
|
28
|
+
violations=()
|
|
29
|
+
fixed=0
|
|
30
|
+
|
|
31
|
+
is_index_file() {
|
|
32
|
+
local base="$1"
|
|
33
|
+
case " $ROLES " in *" ${base%.md} "*) return 0 ;; esac
|
|
34
|
+
return 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Roles this file/entry names as an audience, whitespace-separated, deduped.
|
|
38
|
+
#
|
|
39
|
+
# A declaration must OWN its line. Anchoring here is what keeps the audit from
|
|
40
|
+
# reading its own documentation as data: prose that shows the syntax inline
|
|
41
|
+
# (`<!-- roles: cto, cqo -->` inside backticks) is an example, not a routing,
|
|
42
|
+
# and counting it makes the audit report holes that do not exist.
|
|
43
|
+
declared_roles() {
|
|
44
|
+
{
|
|
45
|
+
grep -oiE '^[[:space:]]*<!--[[:space:]]*roles?:[^>]*-->' "$1" 2>/dev/null | sed -E 's/^[[:space:]]*<!--[[:space:]]*[Rr]oles?:[[:space:]]*//; s/[[:space:]]*-->//'
|
|
46
|
+
grep -oiE '^[[:space:]]*[-*][[:space:]]*\*\*Roles?\*\*:.*' "$1" 2>/dev/null | sed -E 's/.*\*\*[Rr]oles?\*\*:[[:space:]]*//'
|
|
47
|
+
} | tr ',' '\n' | tr -d ' \t' | grep -v '^$' | tr 'A-Z' 'a-z' | sort -u
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Does <index> reach <target basename>? Only a real markdown link target counts.
|
|
51
|
+
# A bare mention of the filename in prose is not a route a reader can follow,
|
|
52
|
+
# and counting it would make the audit report success on exactly the entries it
|
|
53
|
+
# exists to find. Matches `](./file.md`, `](file.md`, with or without an anchor.
|
|
54
|
+
reaches() {
|
|
55
|
+
local index="$1" target="$2"
|
|
56
|
+
[ -f "$index" ] || return 1
|
|
57
|
+
grep -qF -- "](./$target" "$index" || grep -qF -- "]($target" "$index"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
audit_dir() {
|
|
61
|
+
local kind="$1" dir="$PROJECT_ROOT/.harness/$1"
|
|
62
|
+
[ -d "$dir" ] || return 0
|
|
63
|
+
local f base host_role roles role index
|
|
64
|
+
for f in "$dir"/*.md; do
|
|
65
|
+
[ -e "$f" ] || continue
|
|
66
|
+
base="$(basename "$f")"
|
|
67
|
+
[ "$base" = "README.md" ] && continue
|
|
68
|
+
# shared.md is read by every role by construction (Hard Rule 11), so an entry
|
|
69
|
+
# living there is already reachable to every audience it could name.
|
|
70
|
+
[ "$base" = "shared.md" ] && continue
|
|
71
|
+
host_role="${base%.md}"
|
|
72
|
+
roles="$(declared_roles "$f")"
|
|
73
|
+
[ -n "$roles" ] || continue
|
|
74
|
+
while IFS= read -r role; do
|
|
75
|
+
[ -n "$role" ] || continue
|
|
76
|
+
# The host file is reachable to its own role by construction.
|
|
77
|
+
if is_index_file "$base" && [ "$role" = "$host_role" ]; then continue; fi
|
|
78
|
+
case " $ROLES " in *" $role "*) ;; *) continue ;; esac
|
|
79
|
+
# Naming `shared` as an audience is satisfied by construction.
|
|
80
|
+
[ "$role" = "shared" ] && continue
|
|
81
|
+
index="$dir/${role}.md"
|
|
82
|
+
if reaches "$index" "$base"; then continue; fi
|
|
83
|
+
if [ "$FIX" = "--fix" ]; then
|
|
84
|
+
[ -f "$index" ] || printf '# %s — %s\n' "${role}" "${kind}" > "$index"
|
|
85
|
+
if ! grep -qF 'Cross-role Links' "$index"; then
|
|
86
|
+
printf '\n## Cross-role Links\n\nItems written elsewhere that name this role as an audience.\n' >> "$index"
|
|
87
|
+
fi
|
|
88
|
+
printf -- '- [%s](./%s) — declares `%s`\n' "${base%.md}" "$base" "$role" >> "$index"
|
|
89
|
+
fixed=$((fixed + 1))
|
|
90
|
+
else
|
|
91
|
+
violations+=("$kind/$base:$role")
|
|
92
|
+
fi
|
|
93
|
+
done <<EOF
|
|
94
|
+
$roles
|
|
95
|
+
EOF
|
|
96
|
+
done
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
audit_dir conventions
|
|
100
|
+
audit_dir gotchas
|
|
101
|
+
|
|
102
|
+
if [ "$FIX" = "--fix" ]; then
|
|
103
|
+
echo "[corpus-reachability] linked $fixed previously unreachable routing(s)"
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [ "${#violations[@]}" -eq 0 ]; then
|
|
108
|
+
[ "$MODE" = "json" ] && { jq -nc '{ok:true, unreachable:[]}' 2>/dev/null || echo '{"ok":true,"unreachable":[]}'; }
|
|
109
|
+
[ "$MODE" = "text" ] && echo "[corpus-reachability] all declared audiences reachable"
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
if [ "$MODE" = "json" ]; then
|
|
114
|
+
printf '%s\n' "${violations[@]}" | jq -Rcs '
|
|
115
|
+
split("\n")[:-1]
|
|
116
|
+
| map(capture("(?<item>[^:]+):(?<role>.*)"))
|
|
117
|
+
| {ok:false, unreachable:.}
|
|
118
|
+
'
|
|
119
|
+
else
|
|
120
|
+
echo "Corpus reachability violation (AGENTS.md Hard Rule 11):"
|
|
121
|
+
for v in "${violations[@]}"; do
|
|
122
|
+
echo "- item: ${v%%:*}"
|
|
123
|
+
echo " names role '${v#*:}' as an audience, but ${v#*:}'s index does not reach it"
|
|
124
|
+
done
|
|
125
|
+
echo " An entry is registered only when every role it names can reach it by the"
|
|
126
|
+
echo " path that role is told to use. Fix: harness-corpus-reachability.sh <root> text --fix"
|
|
127
|
+
fi
|
|
128
|
+
exit 1
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
# --right "올바른 행동" \
|
|
12
12
|
# --why "근거" \
|
|
13
13
|
# --scope "적용 범위" \
|
|
14
|
-
# --source "evaluator-functional:F-003"
|
|
14
|
+
# --source "evaluator-functional:F-003" \\
|
|
15
|
+
# --roles "cqo, cto"
|
|
15
16
|
#
|
|
16
17
|
# 2) 일괄 등록 (JSON stdin/파일):
|
|
17
18
|
# bash harness-gotcha-register.sh <project-root> --from-json <path>
|
|
@@ -69,6 +70,7 @@ TODAY="$(date +%Y-%m-%d)"
|
|
|
69
70
|
# ─────────────────────────────────────────
|
|
70
71
|
register_one() {
|
|
71
72
|
local target="$1" rule_id="$2" title="$3" wrong="$4" right="$5" why="$6" scope="$7" source="$8"
|
|
73
|
+
local roles="${9:-$target}"
|
|
72
74
|
local file="$GOTCHAS_DIR/${target}.md"
|
|
73
75
|
mkdir -p "$(dirname "$file")"
|
|
74
76
|
|
|
@@ -118,6 +120,7 @@ EOF
|
|
|
118
120
|
{
|
|
119
121
|
echo ""
|
|
120
122
|
echo "### [$g_id] $title <!-- rule_id: $rule_id -->"
|
|
123
|
+
echo "- **Roles**: $roles"
|
|
121
124
|
echo "- **Status**: unverified"
|
|
122
125
|
echo "- **Date**: $TODAY"
|
|
123
126
|
echo "- **Source**: $source"
|
|
@@ -132,6 +135,13 @@ EOF
|
|
|
132
135
|
|
|
133
136
|
echo "[gotcha-register] $target: registered $g_id ($rule_id) — unverified"
|
|
134
137
|
|
|
138
|
+
# Hard Rule 11: an entry filed only under its author is indexed but unreachable
|
|
139
|
+
# to the other roles it names. Link it now, while the audience is still known.
|
|
140
|
+
local reach="$(dirname "$0")/harness-corpus-reachability.sh"
|
|
141
|
+
if [ -x "$reach" ] && [ "$roles" != "$target" ]; then
|
|
142
|
+
bash "$reach" "$PROJECT_ROOT" text --fix >/dev/null 2>&1 || true
|
|
143
|
+
fi
|
|
144
|
+
|
|
135
145
|
# Log to progress.log if present
|
|
136
146
|
local progress_log="$PROJECT_ROOT/.harness/progress.log"
|
|
137
147
|
if [ -f "$progress_log" ]; then
|
|
@@ -141,7 +151,9 @@ EOF
|
|
|
141
151
|
|
|
142
152
|
# ─────────────────────────────────────────
|
|
143
153
|
# JSON 배열에서 일괄 등록
|
|
144
|
-
# schema: [{ target, rule_id, title, wrong, right, why, scope, source }]
|
|
154
|
+
# schema: [{ target, rule_id, title, wrong, right, why, scope, source, roles }]
|
|
155
|
+
# `roles` is the comma-separated list of every role that must be able to FIND
|
|
156
|
+
# this entry — not only the one that wrote it (Hard Rule 11). Defaults to target.
|
|
145
157
|
# ─────────────────────────────────────────
|
|
146
158
|
register_from_json() {
|
|
147
159
|
local json="$1"
|
|
@@ -153,7 +165,7 @@ register_from_json() {
|
|
|
153
165
|
|
|
154
166
|
local i
|
|
155
167
|
for ((i=0; i<count; i++)); do
|
|
156
|
-
local t r ti w ri wh sc so
|
|
168
|
+
local t r ti w ri wh sc so rl
|
|
157
169
|
t=$(echo "$json" | jq -r ".[$i].target // empty")
|
|
158
170
|
r=$(echo "$json" | jq -r ".[$i].rule_id // empty")
|
|
159
171
|
ti=$(echo "$json" | jq -r ".[$i].title // empty")
|
|
@@ -162,12 +174,13 @@ register_from_json() {
|
|
|
162
174
|
wh=$(echo "$json" | jq -r ".[$i].why // empty")
|
|
163
175
|
sc=$(echo "$json" | jq -r ".[$i].scope // \"항상\"")
|
|
164
176
|
so=$(echo "$json" | jq -r ".[$i].source // \"evaluator:auto\"")
|
|
177
|
+
rl=$(echo "$json" | jq -r ".[$i].roles // empty")
|
|
165
178
|
|
|
166
179
|
if [ -z "$t" ] || [ -z "$r" ] || [ -z "$ti" ]; then
|
|
167
180
|
echo "[gotcha-register] skip: missing target/rule_id/title at index $i" >&2
|
|
168
181
|
continue
|
|
169
182
|
fi
|
|
170
|
-
register_one "$t" "$r" "$ti" "$w" "$ri" "$wh" "$sc" "$so"
|
|
183
|
+
register_one "$t" "$r" "$ti" "$w" "$ri" "$wh" "$sc" "$so" "${rl:-$t}"
|
|
171
184
|
done
|
|
172
185
|
}
|
|
173
186
|
|
|
@@ -354,6 +367,7 @@ RIGHT=""
|
|
|
354
367
|
WHY=""
|
|
355
368
|
SCOPE="항상"
|
|
356
369
|
SOURCE="evaluator:auto"
|
|
370
|
+
ROLES_DECL=""
|
|
357
371
|
MODE="single"
|
|
358
372
|
FROM_JSON=""
|
|
359
373
|
|
|
@@ -367,6 +381,7 @@ while [ $# -gt 0 ]; do
|
|
|
367
381
|
--why) WHY="$2"; shift 2 ;;
|
|
368
382
|
--scope) SCOPE="$2"; shift 2 ;;
|
|
369
383
|
--source) SOURCE="$2"; shift 2 ;;
|
|
384
|
+
--roles) ROLES_DECL="$2"; shift 2 ;;
|
|
370
385
|
--from-json) MODE="json"; FROM_JSON="$2"; shift 2 ;;
|
|
371
386
|
--scan-evaluations) MODE="scan"; shift ;;
|
|
372
387
|
--scan-all) MODE="scan-all"; shift ;;
|
|
@@ -380,7 +395,7 @@ case "$MODE" in
|
|
|
380
395
|
echo "[gotcha-register] usage: --target X --rule-id Y --title Z [...]" >&2
|
|
381
396
|
exit 1
|
|
382
397
|
fi
|
|
383
|
-
register_one "$TARGET" "$RULE_ID" "$TITLE" "$WRONG" "$RIGHT" "$WHY" "$SCOPE" "$SOURCE"
|
|
398
|
+
register_one "$TARGET" "$RULE_ID" "$TITLE" "$WRONG" "$RIGHT" "$WHY" "$SCOPE" "$SOURCE" "${ROLES_DECL:-$TARGET}"
|
|
384
399
|
;;
|
|
385
400
|
json)
|
|
386
401
|
if [ ! -f "$FROM_JSON" ]; then echo "[gotcha-register] file not found: $FROM_JSON" >&2; exit 1; fi
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-spec-pin.sh — AGENTS.md Hard Rule 4 (spec-pin clause).
|
|
3
|
+
#
|
|
4
|
+
# A category is complete AGAINST A SPEC VERSION, never in the abstract. Measured:
|
|
5
|
+
# a spec moved v0.7 -> v0.9, changing a response contract, while the category
|
|
6
|
+
# built against v0.7 sat marked complete — two later revisions had landed
|
|
7
|
+
# silently, and nothing in the harness recorded which version the work was for.
|
|
8
|
+
# The symptom was a lookup key that no longer matched: no error, no log, three
|
|
9
|
+
# overlays dropped as null.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# harness-spec-pin.sh <project-root> <mission-rel> add <name> <path> [version]
|
|
13
|
+
# harness-spec-pin.sh <project-root> <mission-rel> verify [text|json]
|
|
14
|
+
# harness-spec-pin.sh <project-root> <mission-rel> list
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
|
|
17
|
+
PROJECT_ROOT="${1:-.}"
|
|
18
|
+
MISSION_REL="${2:-}"
|
|
19
|
+
CMD="${3:-verify}"
|
|
20
|
+
DOCS="$PROJECT_ROOT/.harness/documents"
|
|
21
|
+
PINS="$DOCS/$MISSION_REL/spec-pins.json"
|
|
22
|
+
|
|
23
|
+
command -v jq >/dev/null 2>&1 || { echo "[spec-pin] jq required" >&2; exit 2; }
|
|
24
|
+
[ -n "$MISSION_REL" ] || { echo "[spec-pin] mission path required" >&2; exit 2; }
|
|
25
|
+
|
|
26
|
+
hash_of() {
|
|
27
|
+
local f="$1"
|
|
28
|
+
[ -f "$f" ] || { printf 'MISSING'; return; }
|
|
29
|
+
if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$f" | awk '{print $1}'
|
|
30
|
+
elif command -v sha256sum >/dev/null 2>&1; then sha256sum "$f" | awk '{print $1}'
|
|
31
|
+
else printf 'NOHASHER'; fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case "$CMD" in
|
|
35
|
+
add)
|
|
36
|
+
name="${4:-}"; specpath="${5:-}"; version="${6:-unversioned}"
|
|
37
|
+
[ -n "$name" ] && [ -n "$specpath" ] || { echo "[spec-pin] usage: add <name> <path> [version]" >&2; exit 2; }
|
|
38
|
+
mkdir -p "$(dirname "$PINS")"
|
|
39
|
+
[ -f "$PINS" ] || echo '{"pins":[]}' > "$PINS"
|
|
40
|
+
abs="$specpath"; case "$specpath" in /*) ;; *) abs="$PROJECT_ROOT/$specpath" ;; esac
|
|
41
|
+
h="$(hash_of "$abs")"
|
|
42
|
+
tmp="$(mktemp)"
|
|
43
|
+
jq --arg n "$name" --arg p "$specpath" --arg v "$version" --arg h "$h" \
|
|
44
|
+
'.pins = ((.pins // []) | map(select(.name != $n)) + [{name:$n, path:$p, version:$v, sha256:$h, pinned_at:(now|todate)}])' \
|
|
45
|
+
"$PINS" > "$tmp" && mv "$tmp" "$PINS"
|
|
46
|
+
echo "[spec-pin] pinned $name @ $version ($h)"
|
|
47
|
+
;;
|
|
48
|
+
list)
|
|
49
|
+
[ -f "$PINS" ] && jq -r '.pins[] | "- \(.name) @ \(.version) — \(.path) [\(.sha256[0:12])]"' "$PINS" || echo "(no pins)"
|
|
50
|
+
;;
|
|
51
|
+
verify)
|
|
52
|
+
mode="${4:-text}"
|
|
53
|
+
# No pins file is not a failure: not every mission builds against an external spec.
|
|
54
|
+
if [ ! -f "$PINS" ]; then
|
|
55
|
+
[ "$mode" = "json" ] && jq -nc '{ok:true, drifted:[], pinned:0}'
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
drifted=()
|
|
59
|
+
while IFS=$'\t' read -r name specpath version want; do
|
|
60
|
+
[ -n "$name" ] || continue
|
|
61
|
+
abs="$specpath"; case "$specpath" in /*) ;; *) abs="$PROJECT_ROOT/$specpath" ;; esac
|
|
62
|
+
got="$(hash_of "$abs")"
|
|
63
|
+
[ "$got" = "NOHASHER" ] && continue
|
|
64
|
+
if [ "$got" != "$want" ]; then
|
|
65
|
+
if [ "$got" = "MISSING" ]; then drifted+=("$name|$version|spec file is gone: $specpath")
|
|
66
|
+
else drifted+=("$name|$version|content changed since it was pinned: $specpath"); fi
|
|
67
|
+
fi
|
|
68
|
+
done < <(jq -r '.pins[]? | [.name, .path, .version, .sha256] | @tsv' "$PINS" 2>/dev/null)
|
|
69
|
+
total="$(jq '[.pins[]?] | length' "$PINS" 2>/dev/null || echo 0)"
|
|
70
|
+
if [ "${#drifted[@]}" -eq 0 ]; then
|
|
71
|
+
[ "$mode" = "json" ] && jq -nc --argjson n "$total" '{ok:true, drifted:[], pinned:$n}'
|
|
72
|
+
[ "$mode" = "text" ] && echo "[spec-pin] $total pin(s) still match the current documents"
|
|
73
|
+
exit 0
|
|
74
|
+
fi
|
|
75
|
+
if [ "$mode" = "json" ]; then
|
|
76
|
+
printf '%s\n' "${drifted[@]}" | jq -Rcs 'split("\n")[:-1] | map(split("|") | {name:.[0], pinned_version:.[1], detail:.[2]}) | {ok:false, drifted:.}'
|
|
77
|
+
else
|
|
78
|
+
echo "Spec pin drift (AGENTS.md Hard Rule 4):"
|
|
79
|
+
for d in "${drifted[@]}"; do
|
|
80
|
+
rest="${d#*|}"
|
|
81
|
+
echo "- ${d%%|*} (built against ${rest%%|*})"
|
|
82
|
+
echo " ${d##*|}"
|
|
83
|
+
done
|
|
84
|
+
echo " This mission was built against a spec that has since moved. Re-check the"
|
|
85
|
+
echo " affected work before marking anything complete, then re-pin:"
|
|
86
|
+
echo " scripts/harness-spec-pin.sh . $MISSION_REL add <name> <path> <new-version>"
|
|
87
|
+
fi
|
|
88
|
+
exit 1
|
|
89
|
+
;;
|
|
90
|
+
*) echo "[spec-pin] unknown command: $CMD" >&2; exit 2 ;;
|
|
91
|
+
esac
|
|
@@ -120,6 +120,32 @@ done <<EOF
|
|
|
120
120
|
$mission_dirs
|
|
121
121
|
EOF
|
|
122
122
|
|
|
123
|
+
# Record drift (AGENTS.md Hard Rule 12): a conclusion the session holds but has
|
|
124
|
+
# not written into the state file is not held by the company. Measured cost of
|
|
125
|
+
# the unreconciled case: a finished step the orchestration loop went on trying
|
|
126
|
+
# to spawn 70 times, because only the report knew it was done.
|
|
127
|
+
PROGRESS_FILE="$PROJECT_ROOT/.harness/progress.json"
|
|
128
|
+
if [ -f "$PROGRESS_FILE" ]; then
|
|
129
|
+
while IFS=$'\t' read -r wname wreport; do
|
|
130
|
+
[ -n "$wname" ] && [ -n "$wreport" ] || continue
|
|
131
|
+
report_abs="$wreport"
|
|
132
|
+
case "$wreport" in /*) ;; *) report_abs="$PROJECT_ROOT/$wreport" ;; esac
|
|
133
|
+
[ -f "$report_abs" ] || continue
|
|
134
|
+
# Section-scoped reading: the Status body may be quoted or indented.
|
|
135
|
+
if awk '
|
|
136
|
+
/^[[:space:]]*>?[[:space:]]*##[[:space:]]+Status[[:space:]]*$/ { inb=1; next }
|
|
137
|
+
inb && /^[[:space:]]*>?[[:space:]]*#/ { inb=0 }
|
|
138
|
+
inb && /COMPLETE/ { found=1 }
|
|
139
|
+
END { exit(found ? 0 : 1) }
|
|
140
|
+
' "$report_abs" 2>/dev/null; then
|
|
141
|
+
violations+=("state-file:worker-$wname-reported-COMPLETE-but-progress.json-still-running")
|
|
142
|
+
fi
|
|
143
|
+
done < <(jq -r '
|
|
144
|
+
[.company_state.workers[]? | select((.status // "") | test("running|busy"))
|
|
145
|
+
| [(.name // .worker // .feature // "unknown"), (.report // .report_path // .path // "")]]
|
|
146
|
+
| .[] | @tsv' "$PROGRESS_FILE" 2>/dev/null)
|
|
147
|
+
fi
|
|
148
|
+
|
|
123
149
|
if [ "${#violations[@]}" -eq 0 ]; then
|
|
124
150
|
if [ "$mode" = "json" ]; then
|
|
125
151
|
jq -nc '{ok:true, violations:[]}'
|