@walwal-harness/cli 7.1.53 → 7.1.55

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.
@@ -1,5 +1,13 @@
1
1
  # {{WORKER_TITLE}} — Worker Report
2
2
 
3
+ <!--
4
+ Seeding contract: the owning CXX creates this file WITH EVERY SECTION ALREADY
5
+ PRESENT before the worker starts, and the worker fills it in incrementally as
6
+ the work happens. Do not assemble the report at the end. A worker that dies
7
+ mid-round (rate limit, crash, cancelled session) must leave a valid partial
8
+ report, never a stub.
9
+ -->
10
+
3
11
  ## Status
4
12
  IN_PROGRESS
5
13
 
@@ -23,6 +31,14 @@ The dashboard uses this line (not file timestamps) to show whether the worker is
23
31
  ## Result
24
32
  - Outcome and how it meets the acceptance criteria.
25
33
 
34
+ ## Lessons Tally
35
+ - Corpus items that actually fired during this task: {{LESSONS_FIRED}}
36
+ <!--
37
+ One line. Name the convention/gotcha items from the brief that actually changed
38
+ what you did. `0 fired` is a valid tally and must be stated, not omitted — a
39
+ tally that only ever reports hits trains workers to manufacture them.
40
+ -->
41
+
26
42
  ## Implementation Notes
27
43
 
28
44
  ### Design Decisions
package/bin/init.js CHANGED
@@ -167,6 +167,50 @@ function writeClaudeSettings(settings) {
167
167
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
168
168
  }
169
169
 
170
+ // Pre-accept the workspace-trust gate + MCP auto-approval in the user-level
171
+ // ~/.claude.json so unattended Agent Teams teammates don't block on the trust
172
+ // dialog ("this workspace has not been trusted" → settings.json ignored) or the
173
+ // per-project MCP-server prompt. These two flags live ONLY here — no project
174
+ // file can clear them, and --dangerously-skip-permissions does not touch them.
175
+ // Writes are atomic (temp + rename) with a one-time backup so a malformed write
176
+ // can never corrupt the user's global config; any failure is non-fatal to init.
177
+ function acceptWorkspaceTrust() {
178
+ const claudeJsonPath = path.join(require('os').homedir(), '.claude.json');
179
+ if (!fileExists(claudeJsonPath)) {
180
+ log('Skipped workspace-trust pre-accept: ~/.claude.json not found (run Claude here once)');
181
+ return;
182
+ }
183
+ let raw, cfg;
184
+ try {
185
+ raw = fs.readFileSync(claudeJsonPath, 'utf8');
186
+ cfg = JSON.parse(raw);
187
+ } catch (e) {
188
+ log('Skipped workspace-trust pre-accept: ~/.claude.json did not parse (left untouched)');
189
+ return;
190
+ }
191
+ if (!cfg.projects || typeof cfg.projects !== 'object') cfg.projects = {};
192
+ if (!cfg.projects[PROJECT_ROOT] || typeof cfg.projects[PROJECT_ROOT] !== 'object') {
193
+ cfg.projects[PROJECT_ROOT] = {};
194
+ }
195
+ const p = cfg.projects[PROJECT_ROOT];
196
+ if (p.hasTrustDialogAccepted === true && p.enableAllProjectMcpServers === true) {
197
+ log('Workspace already trusted + project MCP servers auto-approved');
198
+ return;
199
+ }
200
+ try {
201
+ const bak = `${claudeJsonPath}.walwal-bak`;
202
+ if (!fileExists(bak)) fs.writeFileSync(bak, raw);
203
+ p.hasTrustDialogAccepted = true;
204
+ p.enableAllProjectMcpServers = true;
205
+ const tmp = `${claudeJsonPath}.walwal.tmp.${process.pid}`;
206
+ fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2) + '\n');
207
+ fs.renameSync(tmp, claudeJsonPath);
208
+ log('Workspace trust pre-accepted + project MCP auto-approved in ~/.claude.json');
209
+ } catch (e) {
210
+ log(`WARNING: could not update ~/.claude.json trust state: ${e.message}`);
211
+ }
212
+ }
213
+
170
214
  function mergeClaudePermissionAllow(settings, entries) {
171
215
  if (!settings.permissions || typeof settings.permissions !== 'object') {
172
216
  settings.permissions = {};
@@ -812,6 +856,17 @@ function scaffoldHarness() {
812
856
  copyFile(harnessMdSrc, harnessMdDest);
813
857
  }
814
858
 
859
+ // Worker report skeleton — ALWAYS update. CXX agents seed a worker report
860
+ // from this file BEFORE the worker starts, so a worker killed mid-round
861
+ // leaves a valid partial report instead of a stub. (Hard Rule 20 tally +
862
+ // Hard Rule 12 notes are already present in the skeleton.)
863
+ const workerTplSrc = path.join(PKG_ROOT, 'assets', 'templates', 'worker-report.md.template');
864
+ const workerTplDest = path.join(HARNESS_DIR, 'shared', 'templates', 'worker-report.md');
865
+ if (fs.existsSync(workerTplSrc)) {
866
+ ensureDir(path.dirname(workerTplDest));
867
+ copyFile(workerTplSrc, workerTplDest);
868
+ }
869
+
815
870
  // Copy memory.md (shared learnings)
816
871
  const memorySrc = path.join(PKG_ROOT, 'assets', 'templates', 'memory.md');
817
872
  const memoryDest = path.join(HARNESS_DIR, 'memory.md');
@@ -1347,6 +1402,17 @@ function installAgentTeamsEnv() {
1347
1402
  log('Agent Teams already enabled');
1348
1403
  }
1349
1404
 
1405
+ // Auto-approve project .mcp.json servers. Each Agent Teams teammate is a fresh
1406
+ // `claude` process that otherwise blocks on the interactive "New MCP server
1407
+ // found in this project" prompt (the launch command uses --permission-mode
1408
+ // auto, which --dangerously-skip-permissions cannot influence). This is the
1409
+ // persisted equivalent of choosing "Use this and all future MCP servers".
1410
+ if (settings.enableAllProjectMcpServers !== true) {
1411
+ settings.enableAllProjectMcpServers = true;
1412
+ changed = true;
1413
+ log('All project MCP servers auto-approved (enableAllProjectMcpServers=true)');
1414
+ }
1415
+
1350
1416
  // Add Claude Code permissions required for harness-driven edits, scripts,
1351
1417
  // and parallel worker isolation. Existing permissions are preserved.
1352
1418
  const harnessPerms = [
@@ -1366,6 +1432,10 @@ function installAgentTeamsEnv() {
1366
1432
  writeClaudeSettings(settings);
1367
1433
  log('Claude settings merged: harness permissions/env preserved with existing settings');
1368
1434
  }
1435
+
1436
+ // Clear the user-level gates that the above settings.json cannot satisfy on
1437
+ // their own (workspace trust + per-project MCP approval).
1438
+ acceptWorkspaceTrust();
1369
1439
  }
1370
1440
 
1371
1441
  const HARNESS_HEADER_MARKER = '<!-- walwal-harness:managed-header -->';
@@ -1646,6 +1716,7 @@ function detectMigrationNeeded() {
1646
1716
  configRuntimeVerificationMissing: false,
1647
1717
  configWakeModelMissing: false,
1648
1718
  configWriteOnSignalMissing: false,
1719
+ configLessonsGateMissing: false,
1649
1720
  coreSkillsStale: false,
1650
1721
  hrResourcePoolStale: false,
1651
1722
  harnessMdStale: false,
@@ -1758,6 +1829,12 @@ function detectMigrationNeeded() {
1758
1829
  if (c.company_mode && c.company_mode.write_on_signal === undefined) {
1759
1830
  flags.configWriteOnSignalMissing = true;
1760
1831
  }
1832
+ if (
1833
+ (c.behavior && c.behavior.lessons_gate === undefined) ||
1834
+ (c.company_mode && c.company_mode.worker_model === undefined)
1835
+ ) {
1836
+ flags.configLessonsGateMissing = true;
1837
+ }
1761
1838
  if (
1762
1839
  c.behavior?.auto_route_dispatcher !== undefined ||
1763
1840
  c.behavior?.auto_route_dispatcher_description ||
@@ -2044,6 +2121,9 @@ function showMigrationProposal(flags) {
2044
2121
  }
2045
2122
  if (flags.configWriteOnSignalMissing) {
2046
2123
  console.log(' • config.json: company_mode.write_on_signal 추가 가능');
2124
+ }
2125
+ if (flags.configLessonsGateMissing) {
2126
+ console.log(' • config.json: behavior.lessons_gate + company_mode.worker_model 추가 가능');
2047
2127
  console.log(' 무신호(델타 부재) 틱은 meeting 문서 대신 heartbeat 만 남겨 문서 폭증을 막습니다.');
2048
2128
  }
2049
2129
  if (flags.configLegacyRouting) {
@@ -2130,6 +2210,7 @@ function runMigrate(opts = {}) {
2130
2210
  !flags.configRuntimeVerificationMissing &&
2131
2211
  !flags.configWakeModelMissing &&
2132
2212
  !flags.configWriteOnSignalMissing &&
2213
+ !flags.configLessonsGateMissing &&
2133
2214
  !flags.coreSkillsStale &&
2134
2215
  !flags.hrResourcePoolStale &&
2135
2216
  !flags.harnessMdStale &&
@@ -2217,7 +2298,7 @@ function runMigrate(opts = {}) {
2217
2298
  // 2. config.json — inject company_mode/runtime verification from template if missing
2218
2299
  const configPath = path.join(HARNESS_DIR, 'config.json');
2219
2300
  const tplPath = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
2220
- if ((flags.configMissingCompanyMode || flags.configLegacyRouting || flags.configRuntimeVerificationMissing || flags.configWakeModelMissing || flags.configWriteOnSignalMissing) && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
2301
+ if ((flags.configMissingCompanyMode || flags.configLegacyRouting || flags.configRuntimeVerificationMissing || flags.configWakeModelMissing || flags.configWriteOnSignalMissing || flags.configLessonsGateMissing) && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
2221
2302
  const original = fs.readFileSync(configPath, 'utf8');
2222
2303
  const c = JSON.parse(original);
2223
2304
  const tpl = JSON.parse(fs.readFileSync(tplPath, 'utf8'));
@@ -2255,6 +2336,22 @@ function runMigrate(opts = {}) {
2255
2336
  configChanged = true;
2256
2337
  log(' config.json: write_on_signal (무신호 틱 문서 억제) 섹션 동기화');
2257
2338
  }
2339
+ if (flags.configLessonsGateMissing) {
2340
+ // Hard Rule 20/21: the Stop-hook lessons gate and the explicit worker
2341
+ // model. Both default on; a project can opt out of the gate later.
2342
+ c.behavior = c.behavior || {};
2343
+ if (c.behavior.lessons_gate === undefined) {
2344
+ c.behavior.lessons_gate = tpl.behavior?.lessons_gate ?? true;
2345
+ c.behavior.lessons_gate_description = tpl.behavior?.lessons_gate_description;
2346
+ }
2347
+ c.company_mode = c.company_mode || {};
2348
+ if (c.company_mode.worker_model === undefined) {
2349
+ c.company_mode.worker_model = tpl.company_mode?.worker_model ?? '';
2350
+ c.company_mode.worker_model_description = tpl.company_mode?.worker_model_description;
2351
+ }
2352
+ configChanged = true;
2353
+ log(' config.json: lessons_gate + worker_model (Hard Rule 20/21) 섹션 동기화');
2354
+ }
2258
2355
  if (flags.configRuntimeVerificationMissing && tpl.runtime?.verification) {
2259
2356
  c.runtime = c.runtime || {};
2260
2357
  c.runtime.verification = deepMerge(tpl.runtime.verification, c.runtime.verification || {});
package/commands/goal.md CHANGED
@@ -28,6 +28,8 @@ Required flow:
28
28
  11. Do not invoke internal roles through slash commands; commands are Owner entrypoints only.
29
29
  12. Do not ask the Owner whether to continue, hire workers, choose internal options, or start the next step. If CEO cannot decide alone, convene the relevant CXX agents and decide from their written recommendations. CEO may approve reversible routine operations such as local cron/launchd/wake automation, dashboard refresh, monitoring cadence, Telegram briefing format using existing credentials, and mission consolidation/supersede cleanup. Stop only for external authority such as new credentials/secrets, payment approval, legal/business acceptance, unavailable production access, destructive data action, or direct conflict with stated Owner direction.
30
30
 
31
+ Lessons before plan (AGENTS.md Hard Rule 20): CEO and every CXX read `.harness/conventions/{shared,role}.md` and `.harness/gotchas/{shared,role}.md`, follow only the topic links those files name, and write `## Lessons Preflight` **before** the first edit, the first measurement, and the first worker brief. Every role document and worker report closes with a one-line `## Lessons Tally` immediately above `## Implementation Notes` — `0 fired` is a valid tally and must be stated, not omitted. Requirements a CXX must satisfy that its workers must also satisfy go into the worker brief **verbatim**. The Stop hook enforces this; it is not advisory.
32
+
31
33
  Note: A goal is the company's objective. Submissions and hot-fixes that happen while pursuing it should be recorded under that goal directory.
32
34
 
33
35
  Harness documents (ceo.md, cto.md, cqo.md, worker reports) are mission records, not derived output documents. A docmeta skip decision on these files does not authorize skipping any harness protocol step.
@@ -28,6 +28,10 @@ Required flow:
28
28
  13. Do not invoke internal roles through slash commands; commands are Owner entrypoints only.
29
29
  14. Do not ask the Owner whether to continue, hire workers, choose internal options, or start the next step. If CEO cannot decide alone, convene the relevant CXX agents and decide from their written recommendations. CEO may approve reversible routine operations such as local cron/launchd/wake automation, dashboard refresh, monitoring cadence, Telegram briefing format using existing credentials, and mission consolidation/supersede cleanup. Stop only for external authority such as new credentials/secrets, payment approval, legal/business acceptance, unavailable production access, destructive data action, or direct conflict with stated Owner direction.
30
30
 
31
+ Lessons before plan (AGENTS.md Hard Rule 20): CEO and every CXX read `.harness/conventions/{shared,role}.md` and `.harness/gotchas/{shared,role}.md`, follow only the topic links those files name, and write `## Lessons Preflight` **before** the first edit, the first measurement, and the first worker brief. Every role document and worker report closes with a one-line `## Lessons Tally` immediately above `## Implementation Notes` — `0 fired` is a valid tally and must be stated, not omitted. Requirements a CXX must satisfy that its workers must also satisfy go into the worker brief **verbatim**. The Stop hook enforces this; it is not advisory.
32
+
33
+ Scale the read to the fix. A four-line patch pays the index files plus only the topic links that match the fix — not the whole corpus. The ordering constraint holds at every size; the depth does not.
34
+
31
35
  Note: `/hot-fix` is a problem-fix flow while pursuing the active goal. It belongs under that goal in history.
32
36
 
33
37
  Harness documents (ceo.md, cto.md, cqo.md, worker reports) are mission records, not derived output documents. A docmeta skip decision on these files does not authorize skipping any harness protocol step.
@@ -28,6 +28,8 @@ Required flow:
28
28
  13. Do not invoke internal roles through slash commands; commands are Owner entrypoints only.
29
29
  14. Do not ask the Owner whether to continue, hire workers, choose internal options, or start the next step. If CEO cannot decide alone, convene the relevant CXX agents and decide from their written recommendations. CEO may approve reversible routine operations such as local cron/launchd/wake automation, dashboard refresh, monitoring cadence, Telegram briefing format using existing credentials, and mission consolidation/supersede cleanup. Stop only for external authority such as new credentials/secrets, payment approval, legal/business acceptance, unavailable production access, destructive data action, or direct conflict with stated Owner direction.
30
30
 
31
+ Lessons before plan (AGENTS.md Hard Rule 20): CEO and every CXX read `.harness/conventions/{shared,role}.md` and `.harness/gotchas/{shared,role}.md`, follow only the topic links those files name, and write `## Lessons Preflight` **before** the first edit, the first measurement, and the first worker brief. Every role document and worker report closes with a one-line `## Lessons Tally` immediately above `## Implementation Notes` — `0 fired` is a valid tally and must be stated, not omitted. Requirements a CXX must satisfy that its workers must also satisfy go into the worker brief **verbatim**. The Stop hook enforces this; it is not advisory.
32
+
31
33
  Note: `/submission` is not a new company goal and not an emergency fix. It is an additional requirement while pursuing the active goal. It belongs under that goal in history.
32
34
 
33
35
  Submission request:
@@ -5,3 +5,5 @@
5
5
  - CQO hires evaluators and reviewers before assigning specialist quality work.
6
6
  - No archive is accepted without evidence.
7
7
  - Verified recurring lessons are promoted to `.harness/conventions/`, `.harness/gotchas/`, `.harness/memories/`, or `.harness/shared/`.
8
+ - Negative evidence is inadmissible without a positive control that fires in the same run and varies the exact variable under suspicion. A verdict resting on an unproven negative is BLOCKED, not PASS.
9
+ - Where an instrument comes from a dependency rather than in-repo code, its filtering behaviour is read from source and quoted (package, version, file, line range), not inferred from observed output.
@@ -7,3 +7,4 @@
7
7
  - OPS records operating decisions in `.harness/documents/{mission_name}/ops.md`.
8
8
  - OPS writes daily logs under `.harness/logs/YYYY-MM-DD/`.
9
9
  - OPS logs every non-good-case result and raises emergency events to CEO, CTO, and CQO.
10
+ - OPS records the instrument behind every negative claim — tool, log level, filter, and the positive control that fired in the same run. An unrecorded instrument makes the run's negative results unusable.
@@ -24,6 +24,21 @@
24
24
  - CXX decisions use `{cxx}.md`.
25
25
  - Worker reports use `{owning-cxx}/workers/{worker-name}.md`.
26
26
 
27
+ ## Section-Scoped Reading
28
+
29
+ Any reader that scans a role document, worker report, or mission record for sections — a script, a hook, an agent following a protocol — matches:
30
+
31
+ ```
32
+ ^>?\s*#{1,6}
33
+ ```
34
+
35
+ and returns **every** hit. Blockquoted or plain, at any depth, with **no content filter of any kind**.
36
+
37
+ - **Do not filter headings by what they appear to say.** A filter encodes the reader's guess about which headings matter, and a document is free to put anything in a heading: verdicts, in-place retractions, standing rules, continuation lines.
38
+ - A verdict-token filter and a retraction-marker filter, measured against real CXX documents, caught 3/11 and 4/11 blockquoted headings; their union still missed 4 — including the second line of a verdict, whose first line was caught. **A filter that catches half a verdict is worse than one that catches none, because it reports success.**
39
+ - A reader anchored on `^#` reads a claim and never reaches the in-place retraction posted below it as `> ## …`. That is worse than missing a section: it returns superseded content as current.
40
+ - **The reader that decides what is important before reading is the failure.**
41
+
27
42
  ## DDD
28
43
 
29
44
  - Keep domain, application, interface, and infrastructure decisions distinct.
package/gotchas/cqo.md CHANGED
@@ -7,3 +7,15 @@ Do not approve archive based on narrative confidence. Require concrete quality e
7
7
  ## Unpromoted Recurrence
8
8
 
9
9
  Do not leave repeated failures only in chat or mission notes. Promote verified recurrence prevention to conventions, gotchas, memories, or shared state.
10
+
11
+ ## Negative Evidence Without A Positive Control
12
+
13
+ "No error was logged", "no stubbed 2xx was served", "no leak was detected" are claims about the instrument, not about the system, until a positive control fires in the same run and varies the exact variable under suspicion. Do not accept a clean negative result whose instrument was never shown to be able to see the failure.
14
+
15
+ ## Null Instrument Supplied By A Dependency
16
+
17
+ An instrument's filtering behaviour is read from source and quoted — package, version, file, line range — never inferred from observed output. A log filter that lives in a dependency is invisible to every in-repo search, so its absence from the project's own code proves nothing. When an instrument turns out to have been structurally null, re-open the affected claims **by their shape** — every claim of that form, not only the one that happened to be noticed.
18
+
19
+ ## Audit Questions That Offer Alternatives
20
+
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.
package/gotchas/cto.md CHANGED
@@ -7,3 +7,7 @@ Do not assign architecture, backend, frontend, DevOps, and evaluation to one gen
7
7
  ## Missing Boundary
8
8
 
9
9
  Do not start implementation before domain, API, platform, account, and integration boundaries are explicit enough for workers.
10
+
11
+ ## Stub Report From A Worker Killed Mid-Round
12
+
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.
package/gotchas/ops.md CHANGED
@@ -15,3 +15,7 @@ Do not monitor a production/service endpoint as live until the Owner-provided se
15
15
  ## Port Drift
16
16
 
17
17
  Do not let CXX agents choose arbitrary ports. CEO must set `HARNESS_BASE_PORT` as a `{xx}000` value in `.env`, and services must derive their ports above that base.
18
+
19
+ ## Clean Log From An Unproven Instrument
20
+
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.
package/gotchas/shared.md CHANGED
@@ -11,3 +11,21 @@ Do not add internal slash commands for CXX or workers. Commands are Owner entryp
11
11
  ## Runtime State In Package Repo
12
12
 
13
13
  Do not create or commit project runtime `.harness/` state in this package repository.
14
+
15
+ ## Written, Indexed, And Still Too Late
16
+
17
+ A lesson that is written, indexed, and reachable still costs a mission if it is read *after* the mistake. The corpus is rarely the problem; the ordering is. Read `conventions/` and `gotchas/` **before** the first edit, the first measurement, and the first brief — then write the plan.
18
+
19
+ Do not answer this with a distilled checklist file. A derived corpus must be re-synced whenever any source file changes, goes stale silently, and becomes a second thing nobody reads before planning.
20
+
21
+ ## Silent Worker Is A Rate Limit
22
+
23
+ A worker terminated by a usage limit is indistinguishable, from the outside, from a worker that finished. A silent or truncated round is a rate limit until proven otherwise — check the limit and its reset time before re-briefing, re-hiring, or rewriting the task. This is why every spawn must declare its model: without a declared model there is nothing to check the limit against.
24
+
25
+ ## Heading Readers That Filter By Content
26
+
27
+ A reader anchored on `^#` misses the same heading written as `> ## …`, which is exactly how in-place retractions, verdict continuations, and standing rules get posted. Match `^>?\s*#{1,6}` and return every hit with no content filter. A filter that catches half a verdict is worse than one that catches none, because it reports success.
28
+
29
+ ## Rules Stated One Layer Above The Executing Layer
30
+
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "7.1.53",
3
+ "version": "7.1.55",
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"
@@ -309,7 +309,7 @@ fi
309
309
  # that needs execution, or a change in verdict since the last recorded review.
310
310
  # Control flow (conductor-tick.sh) routes off progress.json state, not this file,
311
311
  # so suppressing the markdown on quiet ticks is safe.
312
- write_on_signal="$(jq -r '.company_mode.write_on_signal // true' "$CONFIG" 2>/dev/null || echo true)"
312
+ write_on_signal="$(jq -r 'if .company_mode.write_on_signal == null then true else .company_mode.write_on_signal end' "$CONFIG" 2>/dev/null || echo true)"
313
313
  last_verdict="$(jq -r '.meetings.last_reason // ""' "$PROGRESS" 2>/dev/null || echo "")"
314
314
  has_signal=true
315
315
  if [ "$write_on_signal" = "true" ]; then
@@ -0,0 +1,130 @@
1
+ #!/bin/bash
2
+ # harness-lessons-gate.sh — enforce "lessons precede planning" where the Stop
3
+ # hook already stops the turn (AGENTS.md Hard Rule 20).
4
+ #
5
+ # A role document that was written without reading the corpus is invisible; a
6
+ # role document that names which corpus items it applied, and then tallies which
7
+ # of them actually fired, is not. Rules that live only in prose are followed
8
+ # when convenient — this one gets the mechanism that already works.
9
+ #
10
+ # Checks every role document of the in-scope mission for:
11
+ # ## Lessons Preflight — which convention/gotcha items apply, and why
12
+ # ## Lessons Tally — one line: which of them actually fired ("0 fired" is valid)
13
+ #
14
+ # Usage: harness-lessons-gate.sh <project-root> [text|json] [all|latest-active]
15
+ set -euo pipefail
16
+
17
+ PROJECT_ROOT="${1:-.}"
18
+ DOC_ROOT="$PROJECT_ROOT/.harness/documents"
19
+ CONFIG="$PROJECT_ROOT/.harness/config.json"
20
+
21
+ [ -d "$DOC_ROOT" ] || exit 0
22
+
23
+ mode="${2:-text}"
24
+ scope="${3:-all}"
25
+
26
+ # Opt-out for projects that have not adopted the sections yet.
27
+ if command -v jq >/dev/null 2>&1 && [ -f "$CONFIG" ]; then
28
+ # jq's `//` treats `false` as empty, so `.x // true` can never return false.
29
+ # Test for null explicitly or the opt-out silently does nothing.
30
+ enabled=$(jq -r 'if .behavior.lessons_gate == null then true else .behavior.lessons_gate end' "$CONFIG" 2>/dev/null || echo true)
31
+ if [ "$enabled" != "true" ]; then
32
+ [ "$mode" = "json" ] && jq -nc '{ok:true, violations:[], skipped:"lessons_gate disabled"}'
33
+ exit 0
34
+ fi
35
+ fi
36
+
37
+ violations=()
38
+
39
+ # Section-scoped reading (conventions/shared.md): match `^>?\s*#{1,6}` and take
40
+ # every hit — blockquoted or plain, at any depth, with no content filter.
41
+ HEADING2='^[[:space:]]*>?[[:space:]]*##[[:space:]]+'
42
+
43
+ has_section() {
44
+ local file="$1" title="$2"
45
+ [ -s "$file" ] || return 1
46
+ grep -Eq "${HEADING2}${title}[[:space:]]*$" "$file"
47
+ }
48
+
49
+ is_terminal_lifecycle() {
50
+ case "$1" in
51
+ closed|cancelled|superseded|complete|completed|blocked) return 0 ;;
52
+ *) return 1 ;;
53
+ esac
54
+ }
55
+
56
+ mission_mtime() {
57
+ stat -f '%m' "$1" 2>/dev/null || stat -c '%Y' "$1" 2>/dev/null || echo 0
58
+ }
59
+
60
+ mission_dirs=$(find "$DOC_ROOT" -type f \( -name 'ceo.md' -o -name 'coo.md' -o -name 'cdo.md' -o -name 'cto.md' -o -name 'cqo.md' -o -name 'ops.md' \) -exec dirname {} \; 2>/dev/null | sort -u)
61
+
62
+ if [ "$scope" = "latest-active" ]; then
63
+ latest_active_mission=""
64
+ latest_active_mtime=0
65
+ while IFS= read -r mission_dir; do
66
+ [ -n "$mission_dir" ] || continue
67
+ state_path="$mission_dir/mission-state.json"
68
+ [ -f "$state_path" ] || continue
69
+ command -v jq >/dev/null 2>&1 || continue
70
+ active=$(jq -r '.active // false' "$state_path" 2>/dev/null || echo false)
71
+ lifecycle=$(jq -r '.lifecycle // .status // "unknown"' "$state_path" 2>/dev/null || echo unknown)
72
+ if [ "$active" != "true" ] || is_terminal_lifecycle "$lifecycle"; then
73
+ continue
74
+ fi
75
+ mtime=$(mission_mtime "$mission_dir")
76
+ if [ "${mtime:-0}" -ge "${latest_active_mtime:-0}" ]; then
77
+ latest_active_mtime="$mtime"
78
+ latest_active_mission="$mission_dir"
79
+ fi
80
+ done <<EOF
81
+ $mission_dirs
82
+ EOF
83
+ mission_dirs="$latest_active_mission"
84
+ fi
85
+
86
+ while IFS= read -r mission_dir; do
87
+ [ -n "$mission_dir" ] || continue
88
+ [ -d "$mission_dir" ] || continue
89
+ mission_name="${mission_dir#"$DOC_ROOT"/}"
90
+
91
+ for role in ceo coo cdo cto cqo ops; do
92
+ role_path="$mission_dir/$role.md"
93
+ [ -s "$role_path" ] || continue
94
+ if ! has_section "$role_path" "Lessons Preflight"; then
95
+ violations+=("$mission_name:$role.md-missing-lessons-preflight")
96
+ fi
97
+ if ! has_section "$role_path" "Lessons Tally"; then
98
+ violations+=("$mission_name:$role.md-missing-lessons-tally")
99
+ fi
100
+ done
101
+ done <<EOF
102
+ $mission_dirs
103
+ EOF
104
+
105
+ if [ "${#violations[@]}" -eq 0 ]; then
106
+ if [ "$mode" = "json" ]; then
107
+ jq -nc '{ok:true, violations:[]}' 2>/dev/null || echo '{"ok":true,"violations":[]}'
108
+ fi
109
+ exit 0
110
+ fi
111
+
112
+ if [ "$mode" = "json" ]; then
113
+ printf '%s\n' "${violations[@]}" | jq -Rcs '
114
+ split("\n")[:-1]
115
+ | map(capture("(?<mission>[^:]+):(?<docs>.*)") | .docs = (.docs | split(" ")))
116
+ | {ok:false, violations:.}
117
+ '
118
+ else
119
+ echo "Lessons-before-plan violation (AGENTS.md Hard Rule 20):"
120
+ for violation in "${violations[@]}"; do
121
+ mission="${violation%%:*}"
122
+ docs="${violation#*:}"
123
+ echo "- mission: $mission"
124
+ echo " issue: $docs"
125
+ echo " required: '## Lessons Preflight' (which convention/gotcha items apply, and why)"
126
+ echo " '## Lessons Tally' (one line: which fired; '0 fired' is valid and must be stated)"
127
+ done
128
+ fi
129
+
130
+ exit 1
@@ -25,7 +25,9 @@ CONFIG="$CWD/.harness/config.json"
25
25
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
26
26
 
27
27
  # Opt-out
28
- AUTO_CHAIN=$(jq -r '.behavior.auto_chain_on_stop // true' "$CONFIG" 2>/dev/null || echo "true")
28
+ # `.x // true` would collapse an explicit `false` back to `true` (jq treats
29
+ # false as empty), which silently disabled this documented opt-out.
30
+ AUTO_CHAIN=$(jq -r 'if .behavior.auto_chain_on_stop == null then true else .behavior.auto_chain_on_stop end' "$CONFIG" 2>/dev/null || echo "true")
29
31
  if [ "$AUTO_CHAIN" != "true" ]; then exit 0; fi
30
32
 
31
33
  # 무한루프 방지: 한 sprint 안에서 stop_chain_count 가 상한을 넘으면 중단
@@ -167,6 +169,28 @@ if [ -x "$SCRIPT_DIR/harness-worker-evidence-validate.sh" ]; then
167
169
  fi
168
170
  fi
169
171
 
172
+ # Lessons-before-plan gate (AGENTS.md Hard Rule 20):
173
+ # 코퍼스를 읽고 계획을 세운 흔적이 role 문서에 없으면 턴을 끝내지 못하게 막는다.
174
+ # 산문으로만 존재하는 규칙은 편할 때만 지켜진다 — 이미 동작하는 정지 메커니즘에
175
+ # 검사를 붙인다. 활성 미션 1개로 스코프를 좁혀 legacy/archive 문서가 영구히
176
+ # Stop 을 막지 않게 한다. .harness/config.json 의 behavior.lessons_gate=false 로
177
+ # 아직 채택하지 않은 프로젝트는 opt-out 할 수 있다.
178
+ if [ -x "$SCRIPT_DIR/harness-lessons-gate.sh" ]; then
179
+ LESSONS_JSON=$("$SCRIPT_DIR/harness-lessons-gate.sh" "$CWD" json latest-active 2>/dev/null || true)
180
+ LESSONS_OK=$(echo "$LESSONS_JSON" | jq -r 'if has("ok") then .ok else true end' 2>/dev/null || echo true)
181
+ if [ "$LESSONS_OK" != "true" ]; then
182
+ REASON=$(echo "$LESSONS_JSON" | jq -r '
183
+ "교훈 선행 게이트(Hard Rule 20): " +
184
+ ([.violations[] | "\(.mission) → \(.docs | join(","))"] | join("; ")) +
185
+ ". 각 role 문서에 `## Lessons Preflight`(이 미션에 적용되는 conventions/gotchas 항목과 이유)와 " +
186
+ "그 아래 `## Implementation Notes` 바로 앞에 한 줄짜리 `## Lessons Tally`(실제로 발동한 항목; `0 fired` 도 유효하며 생략은 불가)를 " +
187
+ "추가한 뒤 계속하라. 코퍼스를 다시 요약한 별도 체크리스트 파일을 만들지 말 것 — 읽는 순서를 고치는 규칙이다."
188
+ ' 2>/dev/null || echo "교훈 선행 게이트(Hard Rule 20): active mission 의 role 문서에 ## Lessons Preflight / ## Lessons Tally 가 없습니다. 먼저 추가하세요.")
189
+ jq -nc --arg reason "$REASON" '{decision:"block", reason:$reason}'
190
+ exit 0
191
+ fi
192
+ fi
193
+
170
194
  # stop_chain_count 증가 (느슨한 카운터 — race 허용)
171
195
  NEW_COUNT=$((STOP_CHAIN_COUNT + 1))
172
196
  TMP=$(mktemp)
@@ -16,7 +16,7 @@ STRUCTURED_LIB="$SCRIPT_DIR/lib/harness-structured-log.sh"
16
16
  # 조건 2: opt-out 플래그 확인
17
17
  AUTO_ROUTE="true"
18
18
  if command -v jq >/dev/null 2>&1; then
19
- AUTO_ROUTE=$(jq -r '.behavior.auto_route_ceo // .behavior.auto_route_dispatcher // true' "$CWD/.harness/config.json" 2>/dev/null || echo "true")
19
+ AUTO_ROUTE=$(jq -r 'if .behavior.auto_route_ceo != null then .behavior.auto_route_ceo elif .behavior.auto_route_dispatcher != null then .behavior.auto_route_dispatcher else true end' "$CWD/.harness/config.json" 2>/dev/null || echo "true")
20
20
  fi
21
21
  if [ "$AUTO_ROUTE" != "true" ]; then exit 0; fi
22
22
 
@@ -130,7 +130,7 @@ start_headless_tick() {
130
130
  local idle="$2"
131
131
  local force="$3"
132
132
  local review_path="$4"
133
- local mode executor wake_model agent_bin stamp ops_dir activity_dir prompt_scratch log_rel log_path pid_file old_pid pid status tmux_session session_name session_safe wake_prompt log_note
133
+ local mode executor wake_model agent_bin auto_approve_flag stamp ops_dir activity_dir prompt_scratch log_rel log_path pid_file old_pid pid status tmux_session session_name session_safe wake_prompt log_note
134
134
 
135
135
  if command -v jq >/dev/null 2>&1 && [ -f "$project_root/.harness/config.json" ]; then
136
136
  mode="${HARNESS_WAKE_MODE:-$(jq -r '.company_mode.hourly_wake_mode // "headless"' "$project_root/.harness/config.json" 2>/dev/null || echo headless)}"
@@ -141,6 +141,13 @@ start_headless_tick() {
141
141
  executor="${HARNESS_WAKE_EXECUTOR:-claude}"
142
142
  wake_model="${HARNESS_WAKE_MODEL:-}"
143
143
  fi
144
+ # Autonomous wake agents run unattended: accept all tool/MCP usage so the tick
145
+ # never blocks on a permission prompt nobody is there to answer (default = accept all).
146
+ if [ "$executor" = "codex" ]; then
147
+ auto_approve_flag="--dangerously-bypass-approvals-and-sandbox"
148
+ else
149
+ auto_approve_flag="--dangerously-skip-permissions"
150
+ fi
144
151
  stamp="$(date -u "+%Y%m%dT%H%M%SZ")"
145
152
  ops_dir="$project_root/.harness/ops/wake"
146
153
  activity_dir="$project_root/.harness/activity"
@@ -237,12 +244,12 @@ EOF
237
244
  (
238
245
  cd "$project_root" || exit 1
239
246
  if [ "$executor" = "codex" ]; then
240
- "$agent_bin" exec -C "$project_root" "$wake_prompt" > "$log_path" 2>&1
247
+ "$agent_bin" exec $auto_approve_flag -C "$project_root" "$wake_prompt" > "$log_path" 2>&1
241
248
  else
242
249
  if [ -n "$wake_model" ]; then
243
- "$agent_bin" -p "$wake_prompt" --model "$wake_model" > "$log_path" 2>&1
250
+ "$agent_bin" $auto_approve_flag -p "$wake_prompt" --model "$wake_model" > "$log_path" 2>&1
244
251
  else
245
- "$agent_bin" -p "$wake_prompt" > "$log_path" 2>&1
252
+ "$agent_bin" $auto_approve_flag -p "$wake_prompt" > "$log_path" 2>&1
246
253
  fi
247
254
  fi
248
255
  ) &
@@ -262,12 +269,12 @@ EOF
262
269
  session_safe="$(basename "$project_root" | tr -c '[:alnum:]_' '_')"
263
270
  session_name="walwal_${session_safe}_${stamp}"
264
271
  if [ "$executor" = "codex" ]; then
265
- tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec -C '$project_root' - < '$prompt_scratch' 2>&1 | tee '$log_path'"
272
+ tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec $auto_approve_flag -C '$project_root' - < '$prompt_scratch' 2>&1 | tee '$log_path'"
266
273
  else
267
274
  if [ -n "$wake_model" ]; then
268
- tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_scratch')\" --model '$wake_model' 2>&1 | tee '$log_path'"
275
+ tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' $auto_approve_flag -p \"\$(cat '$prompt_scratch')\" --model '$wake_model' 2>&1 | tee '$log_path'"
269
276
  else
270
- tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_scratch')\" 2>&1 | tee '$log_path'"
277
+ tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' $auto_approve_flag -p \"\$(cat '$prompt_scratch')\" 2>&1 | tee '$log_path'"
271
278
  fi
272
279
  fi
273
280
  status="spawned-tmux executor=$executor session=$session_name"