harness-everything 0.11.6 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +45 -0
  4. package/README.md +7 -3
  5. package/behavioral-evals/README.md +37 -0
  6. package/behavioral-evals/cases/self-evolve-retrieval-recurrence.yaml +48 -0
  7. package/behavioral-evals/paired-benchmark.js +211 -5
  8. package/ci/contract-integrity.test.js +68 -2
  9. package/ci/mechanism-2a-rule-of-3.test.js +71 -0
  10. package/ci/mechanism-2ah-workflow-lifecycle.test.js +276 -0
  11. package/ci/mechanism-2c-state-persist.test.js +45 -2
  12. package/ci/mechanism-2t-issue-20.test.js +0 -8
  13. package/ci/mechanism-30-opencode-plugin-loadability.test.js +39 -7
  14. package/ci/mechanism-32-paired-benchmark.test.js +139 -0
  15. package/ci/openai-plugin-package.test.js +17 -0
  16. package/ci/self-evolve-loop.test.js +37 -2
  17. package/ci/telemetry.test.js +31 -1
  18. package/docs/architecture.md +17 -0
  19. package/docs/audit.md +52 -5
  20. package/docs/release-evidence.md +3 -0
  21. package/docs/workflow-runtime.md +13 -0
  22. package/docs/workflows/environment-detection.md +47 -37
  23. package/docs/workflows/eval-harness.md +45 -38
  24. package/docs/workflows/fable-discipline.md +45 -33
  25. package/docs/workflows/git-commit.md +53 -45
  26. package/docs/workflows/grill-me.md +36 -38
  27. package/docs/workflows/grill-with-docs.md +36 -46
  28. package/docs/workflows/harness-everything.md +59 -57
  29. package/docs/workflows/improve-codebase-architecture.md +36 -44
  30. package/docs/workflows/install-cognitive-os.md +36 -29
  31. package/docs/workflows/repo-docs.md +29 -40
  32. package/docs/workflows/rewrite-commits.md +40 -36
  33. package/docs/workflows/security-review.md +34 -53
  34. package/docs/workflows/skill-creator.md +39 -56
  35. package/docs/workflows/skill-style.md +35 -40
  36. package/docs/workflows/using-git-worktrees.md +40 -44
  37. package/docs/workflows/verification-loop.md +43 -48
  38. package/docs/workflows/verify-before-claim.md +37 -40
  39. package/docs/workflows/zoom-out.md +39 -51
  40. package/grill-with-docs/ADR-FORMAT.md +22 -0
  41. package/grill-with-docs/SKILL.md +1 -1
  42. package/harness-everything/SKILL.md +1 -1
  43. package/harness-everything/scripts/kernel-router.js +38 -10
  44. package/hooks/scripts/lib/learning-opportunity.js +22 -0
  45. package/hooks/scripts/lib/telemetry.js +40 -0
  46. package/hooks/scripts/lib/workflow-isolation.js +30 -1
  47. package/hooks/scripts/lib/workflow-runtime.js +18 -1
  48. package/hooks/scripts/rule-of-3-tracker.js +17 -9
  49. package/hooks/scripts/state-persist.js +19 -7
  50. package/hooks/scripts/workflow-disposition.js +19 -8
  51. package/hooks/scripts/workflow-gate.js +21 -4
  52. package/multi-agent-workspace/SKILL.md +1 -1
  53. package/multi-agent-workspace/scripts/index_memory.js +28 -0
  54. package/opencode-plugin/plugin.json +1 -1
  55. package/package.json +1 -1
  56. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  57. package/plugins/harness-everything/contract-integrity/scripts/audit.js +536 -0
  58. package/plugins/harness-everything/hooks/scripts/lib/learning-opportunity.js +22 -0
  59. package/plugins/harness-everything/hooks/scripts/lib/telemetry.js +40 -0
  60. package/plugins/harness-everything/hooks/scripts/lib/workflow-isolation.js +30 -1
  61. package/plugins/harness-everything/hooks/scripts/lib/workflow-runtime.js +18 -1
  62. package/plugins/harness-everything/hooks/scripts/rule-of-3-tracker.js +17 -9
  63. package/plugins/harness-everything/hooks/scripts/state-persist.js +19 -7
  64. package/plugins/harness-everything/hooks/scripts/workflow-disposition.js +19 -8
  65. package/plugins/harness-everything/hooks/scripts/workflow-gate.js +21 -4
  66. package/plugins/harness-everything/plugin.json +1 -1
  67. package/plugins/harness-everything/skills/grill-with-docs/ADR-FORMAT.md +22 -0
  68. package/plugins/harness-everything/skills/grill-with-docs/SKILL.md +1 -1
  69. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  70. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +38 -10
  71. package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +1 -1
  72. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/index_memory.js +28 -0
  73. package/plugins/harness-everything/skills/self-evolve/SKILL.md +1 -1
  74. package/plugins/harness-everything/skills/self-evolve/scripts/lesson-candidate.js +52 -0
  75. package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
  76. package/plugins/harness-everything/skills/to-spec/references/process.md +26 -0
  77. package/plugins/harness-everything/skills/to-spec/templates/cli-reference.md +14 -0
  78. package/plugins/harness-everything/skills/to-spec/templates/dev-doc.md +14 -0
  79. package/plugins/harness-everything/skills/to-spec/templates/feature-spec.md +14 -0
  80. package/plugins/harness-everything/skills/to-spec/templates/schema-doc.md +14 -0
  81. package/plugins/harness-everything/skills/to-tickets/SKILL.md +1 -1
  82. package/plugins/harness-everything/skills/to-tickets/references/publishing-and-templates.md +20 -1
  83. package/plugins/harness-everything/skills/verification-loop/SKILL.md +3 -2
  84. package/plugins/harness-everything/skills/verification-loop/references/verification-phases.md +25 -1
  85. package/plugins/harness-everything/skills/verification-loop/scripts/contract-integrity-audit.js +31 -0
  86. package/plugins/harness-everything/skills/verification-loop/templates/verification-report.template.md +3 -0
  87. package/scripts/sync-openai-plugin.js +12 -0
  88. package/self-evolve/SKILL.md +1 -1
  89. package/self-evolve/scripts/lesson-candidate.js +52 -0
  90. package/telemetry/schemas/event.schema.json +141 -22
  91. package/telemetry/scripts/report.js +42 -0
  92. package/to-spec/SKILL.md +1 -1
  93. package/to-spec/references/process.md +26 -0
  94. package/to-spec/templates/cli-reference.md +14 -0
  95. package/to-spec/templates/dev-doc.md +14 -0
  96. package/to-spec/templates/feature-spec.md +14 -0
  97. package/to-spec/templates/schema-doc.md +14 -0
  98. package/to-tickets/SKILL.md +1 -1
  99. package/to-tickets/references/publishing-and-templates.md +20 -1
  100. package/verification-loop/SKILL.md +3 -2
  101. package/verification-loop/references/verification-phases.md +25 -1
  102. package/verification-loop/scripts/contract-integrity-audit.js +31 -0
  103. package/verification-loop/templates/verification-report.template.md +3 -0
  104. package/docs/issue-20-rollup.md +0 -47
  105. package/docs/reviews/pr-142-issue-131.md +0 -89
@@ -1,64 +1,61 @@
1
1
  # Workflow: Verify Before Claim
2
2
 
3
- > Fact-audit discipline requiring empirical verification of external claims, SDK configurations, defaults, and unmeasured performance estimates.
3
+ > Check external framework, SDK, CLI, and API claims plus any unmeasured performance or cost numbers against an authoritative source, then deliver a cited claim, a real measurement, or a labeled estimate.
4
4
 
5
- ---
5
+ Source of truth: `verify-before-claim/SKILL.md`.
6
6
 
7
- ## 1. Skill Behavior Workflow
7
+ Contract summary from SKILL.md — Trigger: external behavior, version/config/exit-code claim, or unmeasured number. Output: official citation, real measurement, or explicitly labeled estimate. State mutation: none; evidence travels with the claim. Gate: official source or real measurement; unresolved claims stay inconclusive. USE FOR: stating how an external framework, SDK, CLI tool, or API behaves; quoting performance, cost, latency, or timing numbers; answering "does X support Y" or version-specific questions; citing defaults, exit codes, config flags, pricing, or rate limits. DO NOT USE FOR: claims about this repository's own code, facts the user supplied directly, generic CS knowledge such as Big-O.
8
8
 
9
- This section visualizes how the `verify-before-claim` skill executes internally, detailing the sequence of operations, state transitions, and evaluation steps.
9
+ ## 1. Skill Behavior Workflow
10
10
 
11
11
  ```mermaid
12
12
  graph TD
13
- Start([About to Assert External Framework / API Behavior or Perf Number]) --> CheckScope{Claim Scope Check}
14
- CheckScope -->|Internal Repo Code| ReadSource["Read Local Source Code -> State Facts"]
15
- CheckScope -->|External Framework / API / Number| CheckNet{Web Fetch / Search / Measurement Available?}
16
-
17
- CheckNet -->|Yes| FetchDocs["WebFetch Official Docs / Measure Real Execution"]
18
- CheckNet -->|No / Offline| EstimateFallback["Explicitly Label Response as Unverified Estimate"]
19
-
20
- FetchDocs --> CiteDocs["Quote Authoritative Source / State Measured Data"]
21
- ReadSource --> End([Verified Output Delivered])
22
- CiteDocs --> End
23
- EstimateFallback --> End
13
+ Claim[External Claim Or Unmeasured Number] --> Scope{Claim Scope?}
14
+ Scope -->|this repo own code| Local[Read Local Source Report Observed Facts]
15
+ Scope -->|external behavior or number| Official[Consult Official Docs First]
16
+ Official --> Numbers{Needs Number?}
17
+ Numbers -->|yes| Measure[Real Measurement Or Labeled Estimate]
18
+ Numbers -->|no| Cite[Return Cited Claim]
19
+ Measure --> Output[Claim With Evidence]
20
+ Cite --> Output
21
+ Local --> Output2[Fact From Source]
22
+ Official -->|contradiction or missing evidence| Inconclusive[Report Inconclusive]
24
23
  ```
25
24
 
26
- ---
27
-
28
25
  ## 2. Triggering and Routing Path
29
26
 
30
- This diagram illustrates how the `verify-before-claim` skill is triggered through user requests or developer actions, and how it integrates or chains together with other companion skills in the Harness OS ecosystem to form unified workflows.
31
-
32
27
  ```mermaid
33
- graph LR
34
- Router["harness-everything / tier-router.js"] -->|Requires fact checking or estimates| VBC["verify-before-claim / SKILL.md"]
35
- VBC -->|Empirical verification helper| RunCode["Run a real Node/terminal measurement"]
36
- VBC -->|Secures quality of decisions in| GWD["grill-with-docs / SKILL.md"]
37
- VBC -->|Verifies assertions in testing suite| TDD["tdd / SKILL.md"]
28
+ graph TD
29
+ ExtBehave[State External Framework SDK CLI API Behavior] --> Skill[verify-before-claim SKILL]
30
+ PerfNum[Quote Perf Cost Latency Timing] --> Skill
31
+ SupportQ[Does X Support Y Version Question] --> Skill
32
+ ConfigQ[Cite Default ExitCode Flag Pricing Limit] --> Skill
33
+ OwnCode[Claim About This Repo Own Code] --> LocalSrc[Read Actual Source]
34
+ UserFact[Fact User Supplied Directly] --> Decline[Do Not Route]
35
+ GenericCS[Generic CS Knowledge BigO] --> Decline
36
+ Skill --> Cited[Cited Claim Or Measurement Or Estimate]
38
37
  ```
39
38
 
40
- ---
41
-
42
- ## 3. Real-World Use Case Flowchart
39
+ ## 3. Real-World Use Case
43
40
 
44
- Here we model concrete real-world scenarios and use cases of the `verify-before-claim` skill, illustrating standard success paths, error handling, or recovery loops.
41
+ A developer is about to assert a version-specific SDK default and a latency number in a design note. The skill routes the default to official documentation for a citation, routes the latency claim to a real measurement, and labels the value an estimate when no run exists. If sources contradict each other or no authoritative source exists, the result is reported as inconclusive rather than asserted as certain, even under time pressure.
45
42
 
46
43
  ```mermaid
47
44
  graph TD
48
- Start["Developer assumes: 'Node.js fs.rmSync returns boolean upon success'"] --> Trigger["verify-before-claim skill triggered"]
49
- Trigger --> ReadDocs["Checks Node.js documentation via WebSearch"]
50
- ReadDocs --> FindTrue["Discovers fs.rmSync returns 'undefined' on success, and throws on failure"]
51
- FindTrue --> TestCode["Executes quick Node snippet to confirm empirical behavior"]
52
- TestCode --> UpdateCode["Refactors proposed catch-blocks to handle exceptions instead of checking booleans"]
53
- UpdateCode --> Done([Potential production runtime crash prevented])
45
+ Draft[Draft Asserts SDK Default And Latency] --> CheckDocs[Check Official Docs For Default]
46
+ CheckDocs --> CheckNum[Require Real Measurement For Number]
47
+ CheckNum -->|measured| Cited2[Cited Default Plus Measured Number]
48
+ CheckNum -->|no run| Labeled[Labeled Estimate]
49
+ CheckDocs -->|conflict or gap| Inconc[Inconclusive Report]
54
50
  ```
55
51
 
56
- ---
52
+ Deep dive: `verify-before-claim/references/verification-guide.md`.
57
53
 
58
54
  ## 4. Verification Check
59
55
 
60
- To ensure that the `verify-before-claim` skill is operating in strict compliance with Harness OS design laws, verify the following:
61
-
62
- - [ ] **Physical Boundary Verification**: The skill boundaries are respected and do not leak context.
63
- - [ ] **State Checkpoint Verification**: The active state is established, validated, and recorded at the beginning and end of each execution branch.
64
- - [ ] **Cognitive Alignment**: The skill conforms to the **Think > Try > Summarize > Record** cognitive loop.
56
+ - [ ] Local-repository claims were settled by reading the actual source, not by external citation
57
+ - [ ] External framework, SDK, CLI, or API behavior used official documentation first with a citation
58
+ - [ ] Performance, cost, latency, or timing numbers came from a real measurement or were explicitly labeled as estimates
59
+ - [ ] Defaults, exit codes, config flags, pricing, and rate limits were cited rather than asserted from memory
60
+ - [ ] Contradictions or missing evidence were reported as inconclusive
61
+ - [ ] No unverified external fact was claimed as certain, even under time pressure
@@ -1,74 +1,62 @@
1
1
  # Workflow: Zoom Out
2
2
 
3
- > High-level reflection report generation to break out of error loops, analyze failure patterns, and backtrack to alternative paths.
3
+ > Reflect-first breaker after three failures: stop edits, rebuild the full picture with read-only tools, write a fact-check report, then resume or escalate.
4
4
 
5
- ---
5
+ Source of truth: `zoom-out/SKILL.md`.
6
6
 
7
- ## 1. Skill Behavior Workflow
7
+ Contract summary from SKILL.md — Trigger: three same-signature failures or an explicit loop/rethink request. Output: fact-checked report ending in `RESUME` or `ESCALATE`. State: writes the session `zoom-out-report.md`; reset may clear breaker state. Gate: `hooks/scripts/rule-of-3.js` plus a valid report; reset only after the second cycle. USE FOR: "you are stuck in a loop", "the same error keeps failing", "rethink your assumptions". DO NOT USE FOR: routine single-failure debugging, greenfield planning without failures.
8
8
 
9
- This section visualizes how the `zoom-out` skill executes internally, detailing the sequence of operations, state transitions, and evaluation steps.
9
+ ## 1. Skill Behavior Workflow
10
10
 
11
11
  ```mermaid
12
12
  graph TD
13
- Start([3 Consecutive Failures / Goal Drift Triggered]) --> CeaseFire["1. Phase 1: Cease Fire - Stop Code Edits Immediately"]
14
- CeaseFire --> Rebuild["2. Phase 2: Rebuild Full Picture using Read-Only Tools"]
15
- Rebuild --> CheckPath{3. Resolve Reflection Report Path}
16
-
17
- CheckPath -->|Session Path Provided| WriteSession["Copy template to Session zoom-out-report.md"]
18
- CheckPath -->|Template / Path Unavailable| WritePlatform["Write to .github/harness-everything/zoom-out-report.md or Inline"]
19
-
20
- WriteSession --> DecisionGate{4. Phase 4: Decision Gate}
21
- WritePlatform --> DecisionGate
22
-
23
- DecisionGate -->|Untried Path Identified| Resume["RESUME: Execute Fresh Diagnosis in TDD / Fable"]
24
- DecisionGate -->|Requirement Conflict / Access Gap| Escalate["ESCALATE: Present 2-3 Options + Recommendation to Human"]
25
-
26
- Resume --> Resolved{5. Problem Cracking Succeeds?}
27
- Resolved -->|Yes| SelfEvolve["Call self-evolve to Persist Insight"]
28
- Resolved -->|No: 3 More Failures| Escalate
29
- SelfEvolve --> End([Reflection Completed & Lessons Persisted])
30
- Escalate --> End
13
+ Third[Third Same Signature Failure] --> Stop[Stop Edits No User Ask Yet]
14
+ Stop --> ReadOnly[ReadOnly Restate Goal Check Files Config Logs]
15
+ ReadOnly --> Fill[Fill zoom-out-report template]
16
+ Fill --> Decide{RESUME or ESCALATE?}
17
+ Decide -->|untried path| Resume[RESUME On Untried Path]
18
+ Decide -->|genuine user decision| Escalate[ESCALATE With Options]
19
+ Resume --> Again{Repeated Breaker Cycle?}
20
+ Again -->|second cycle| Reset[npm run harness reset]
21
+ Reset --> Evolve[Record Insight With self-evolve]
22
+ Again -->|no| Done([Report Complete])
23
+ Escalate --> Done
31
24
  ```
32
25
 
33
- ---
34
-
35
26
  ## 2. Triggering and Routing Path
36
27
 
37
- This diagram illustrates how the `zoom-out` skill is triggered through user requests or developer actions, and how it integrates or chains together with other companion skills in the Harness OS ecosystem to form unified workflows.
38
-
39
28
  ```mermaid
40
- graph LR
41
- RuleOf3["Rule of 3 Circuit Breaker"] -->|Tripped!| ZoomOut["zoom-out / SKILL.md"]
42
- ZoomOut -->|Halts execution for| ActiveWork["Active workspace edits"]
43
- ZoomOut -->|Asks for human direction via| AskQuestions["host user-input mechanism"]
44
- ZoomOut -->|Unlocks workspace state after| HumanReset["Human Partner Response"]
29
+ graph TD
30
+ LoopReq[Stuck In Loop Request] --> Skill[zoom-out SKILL]
31
+ SameErr[Same Error Keeps Failing] --> Skill
32
+ Rethink[Rethink Assumptions Request] --> Skill
33
+ ThreeFail[Three Same Signature Failures] --> Breaker[hooks scripts rule-of-3 js]
34
+ Breaker --> Skill
35
+ SingleFail[Routine Single Failure] --> DirectFix[Fix Directly]
36
+ Greenfield[Greenfield Planning No Failures] --> Fable[fable-mode SKILL]
37
+ Skill --> ReportGate[Valid Report RESUME or ESCALATE]
45
38
  ```
46
39
 
47
- ---
48
-
49
- ## 3. Real-World Use Case Flowchart
50
-
51
- Here we model concrete real-world scenarios and use cases of the `zoom-out` skill, illustrating standard success paths, error handling, or recovery loops.
40
+ ## 3. Real-World Use Case
52
41
 
53
42
  ```mermaid
54
43
  graph TD
55
- Start["Attempting to fix Python import error; fails 3 times consecutively with same trace"] --> Trigger["Rule of 3 circuit breaker trips"]
56
- Trigger --> Lock["Workspace writes locked"]
57
- Trigger --> Awaken["Awaken: Stop error-fixing; review imports design & requirements (Law 2)"]
58
- Awaken --> Collect["Collect error traces and diff history"]
59
- Collect --> WriteReport["Create the session zoom-out-report.md detailing the import cyclic dependency"]
60
- WriteReport --> Present["Present options to human: Option A - Merge imports, Option B - Extract shared modules"]
61
- Present --> UserChoice["User select: 'Option B'"]
62
- UserChoice --> Unlock["Unlock workspace and execute Option B"]
63
- Unlock --> Done([Error loop successfully broken and resolved])
44
+ Fail[Same Test Fails Three Times Same Signature] --> Halt[Stop Edits Rebuild Picture ReadOnly]
45
+ Halt --> Inspect[Restate Goal Check Files Config Logs]
46
+ Inspect --> Write[Fill templates zoom-out-report template md]
47
+ Write --> Choice{Untried Path Exists?}
48
+ Choice -->|yes| Resume2[RESUME On Untried Path]
49
+ Choice -->|no| Esc2[ESCALATE With Options]
50
+ Resume2 -->|repeated cycle| Reset2[npm run harness reset Then self-evolve]
64
51
  ```
65
52
 
66
- ---
53
+ Template: `zoom-out/templates/zoom-out-report.template.md`. Enforcement: `hooks/scripts/rule-of-3.js`. Deep dive: `zoom-out/references/circuit-breaker.md`.
67
54
 
68
55
  ## 4. Verification Check
69
56
 
70
- To ensure that the `zoom-out` skill is operating in strict compliance with Harness OS design laws, verify the following:
71
-
72
- - [ ] **Physical Boundary Verification**: The skill boundaries are respected and do not leak context.
73
- - [ ] **State Checkpoint Verification**: The active state is established, validated, and recorded at the beginning and end of each execution branch.
74
- - [ ] **Cognitive Alignment**: The skill conforms to the **Think > Try > Summarize > Record** cognitive loop.
57
+ - [ ] After three same-signature failures, edits stopped and the same approach was not retried
58
+ - [ ] Goal, files, configuration, and logs were rechecked with read-only tools before any resume
59
+ - [ ] `templates/zoom-out-report.template.md` was filled and the session `zoom-out-report.md` ends in `RESUME` or `ESCALATE`
60
+ - [ ] Resume targeted only an untried path; genuine user decisions were escalated with options
61
+ - [ ] Reset via `npm run harness:reset` happened only after the second cycle, with the insight recorded via `self-evolve`
62
+ - [ ] Routine single-failure debugging and greenfield planning without failures were not routed here
@@ -22,6 +22,28 @@ Only include these when they add genuine value. Most ADRs won't need them.
22
22
  - **Considered Options** — only when the rejected alternatives are worth remembering
23
23
  - **Consequences** — only when non-obvious downstream effects need to be called out
24
24
 
25
+ ## Decision lifecycle and contract lineage
26
+
27
+ Once an ADR is **accepted**, keep its decision and rationale historically stable. Do not rewrite an accepted ADR to make a later design look as though it was always the decision.
28
+
29
+ Use compact metadata when the ADR participates in a behavior contract:
30
+
31
+ ```md
32
+ ADR-ID: ADR-0001
33
+ Status: proposed | accepted | deprecated | superseded by ADR-0002
34
+ Affected requirements: REQ-001, REQ-004
35
+ ```
36
+
37
+ When a later change materially changes the decision or its rationale:
38
+
39
+ 1. create a new sequential ADR;
40
+ 2. state the new decision and why the prior trade-off changed;
41
+ 3. mark the earlier ADR `superseded by ADR-NNNN`;
42
+ 4. update the living spec requirements affected by that decision;
43
+ 5. let the structured contract trace carry detailed ticket/test/implementation lineage.
44
+
45
+ A ticket or implementation note never silently becomes the new architectural source of truth. If only implementation changes and the approved behavior/decision does not, keep the accepted ADR unchanged.
46
+
25
47
  ## Numbering
26
48
 
27
49
  Scan `docs/adr/` for the highest existing number and increment by one.
@@ -4,7 +4,7 @@ description: Challenge the plan against the existing domain model, sharpen termi
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.4.0
7
+ version: 0.12.0
8
8
  ---
9
9
 
10
10
  # Grill With Docs (Domain Modeling & Architectural Alignment)
@@ -4,7 +4,7 @@ description: "Route software/project work through the Harness kernel: classify T
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.11.2
7
+ version: 0.11.7
8
8
  ---
9
9
 
10
10
  # Harness Everything
@@ -33,12 +33,26 @@ function issueMemoryCapability(workflow) {
33
33
  return token;
34
34
  }
35
35
 
36
+ // Host-generated background-task notifications arrive as UserPromptSubmit
37
+ // hook payloads but are not user input (#168). Routing them like prompts
38
+ // replaces the workflow contract mid-turn (e.g. deferred -> a new Tier-2).
39
+ // A prompt that is entirely a notification envelope must never create or
40
+ // replace a workflow; the current contract is kept unchanged.
41
+ function isHostNotificationPrompt(prompt) {
42
+ const text = String(prompt || '').trim();
43
+ if (!text) return false;
44
+ return /^<task-notification[\s>][\s\S]*<\/task-notification>\s*$/i.test(text);
45
+ }
46
+
36
47
  function persistWorkflow(plan, payload, prompt) {
37
48
  if (!payload || !(payload.session_id || payload.sessionId)) return null;
38
49
  const { runtime, hooksRoot } = loadRuntime();
39
50
  const context = runtime.loadWorkflow(payload);
40
51
  const { writeCurrentSession } = require(path.join(hooksRoot, 'lib/harness-state'));
41
52
  writeCurrentSession(context.root, context.sessionId);
53
+ if (isHostNotificationPrompt(prompt)) {
54
+ return { ...context, hooksRoot, retained: true, hostNotification: true };
55
+ }
42
56
  if (context.workflow && runtime.OPEN_STATES.has(context.workflow.state)) {
43
57
  if (!context.workflow.workflowPlan) throw new Error('legacy unresolved workflow needs explicit migration; refusing to replace its obligations');
44
58
  // Steering/status prompts cannot drop an unresolved execution contract.
@@ -92,17 +106,30 @@ function run(raw) {
92
106
  try { payload = JSON.parse(raw); } catch (_) { console.error('[Workflow Router] Invalid hook payload; lifecycle persistence unavailable.'); }
93
107
  }
94
108
  const prompt = promptArg || (typeof payload?.prompt === 'string' ? payload.prompt : '');
95
- const result = core.route(promptArg, raw || null);
96
- let plan = result.contract.workflowPlan;
109
+ const hostNotification = isHostNotificationPrompt(prompt);
110
+ // Host-generated notifications are lifecycle events, not user-authored task
111
+ // input. Never feed their text into the router: when there is an active
112
+ // contract we retain it below; when there is none, the event is a no-op.
113
+ const result = hostNotification ? null : core.route(promptArg, raw || null);
114
+ let plan = result?.contract?.workflowPlan || null;
97
115
  let persisted = null;
98
116
  let failure = null;
99
117
  try {
100
118
  persisted = persistWorkflow(plan, payload, prompt);
101
- if (persisted?.retained) plan = persisted.workflow.workflowPlan;
119
+ if (persisted?.retained && persisted.workflow) plan = persisted.workflow.workflowPlan;
102
120
  } catch (error) { failure = error; }
103
- if (result.sanitized) console.log(result.sanitized);
104
- if (result.stderr) process.stderr.write(result.stderr);
105
- if (persisted?.retained) console.log('\n=> Retaining unresolved workflow from earlier prompts; the checkpoint below is the active execution contract.');
121
+ if (hostNotification && !persisted?.workflow) {
122
+ console.log('\n=> Host notification — no active execution contract; ignored for routing.');
123
+ if (failure) {
124
+ console.error('[Workflow Router] ' + failure.message);
125
+ process.exitCode = 2;
126
+ }
127
+ return;
128
+ }
129
+ if (result?.sanitized) console.log(result.sanitized);
130
+ if (result?.stderr) process.stderr.write(result.stderr);
131
+ if (persisted?.hostNotification) console.log('\n=> Host notification — routing unchanged; keeping the active execution contract.');
132
+ else if (persisted?.retained) console.log('\n=> Retaining unresolved workflow from earlier prompts; the checkpoint below is the active execution contract.');
106
133
  core.printWorkflowPlan(plan);
107
134
  core.printRoutingCheckpoint(plan);
108
135
  core.printKernelContract(plan);
@@ -116,7 +143,7 @@ function run(raw) {
116
143
  console.log(' - Tier 3 / Fable source and artifact mutations require Git worktree isolation; unavailable isolation means BLOCKED.');
117
144
  console.log(' - Escape is limited to workflow-uncovered-scope or host-capability-unavailable, with stage, scope and evidence; other obligations remain mandatory.');
118
145
  }
119
- if (persisted) {
146
+ if (persisted?.workflow) {
120
147
  const controller = path.join(persisted.hooksRoot, 'workflow-disposition.js');
121
148
  console.log(' - Workflow id: ' + persisted.workflow.workflowId);
122
149
  console.log(' - Memory write disposition: ' + (persisted.workflow.workflowPlan.memory?.write || 'none'));
@@ -130,12 +157,13 @@ function run(raw) {
130
157
  if (failure) {
131
158
  console.error('[Workflow Router] ' + failure.message);
132
159
  process.exitCode = 2;
133
- } else if (result.status !== 0) process.exitCode = result.status || 1;
160
+ } else if (result && result.status !== 0) process.exitCode = result.status || 1;
134
161
  }
135
162
 
163
+ if (typeof module !== 'undefined') module.exports = { isHostNotificationPrompt };
164
+
136
165
  if (process.argv.length > 2 || process.stdin.isTTY) run('');
137
- else {
138
- let input = '';
166
+ else { let input = '';
139
167
  let finished = false;
140
168
  const finish = () => { if (finished) return; finished = true; clearTimeout(timeout); run(input); };
141
169
  const timeout = setTimeout(finish, 500);
@@ -8,6 +8,7 @@ const {
8
8
  getSessionId,
9
9
  getSessionDir,
10
10
  } = require('./harness-state');
11
+ const { emitLessonTelemetry } = require('./telemetry');
11
12
 
12
13
  const SCHEMA_VERSION = 1;
13
14
  const TRIGGERS = new Set(['rule-of-3-recovery', 'verifier-fail-pass']);
@@ -134,6 +135,27 @@ function createLearningOpportunity(payload, input = {}) {
134
135
  updatedAt: observedAt,
135
136
  };
136
137
  atomicWriteJson(file, candidate);
138
+ const lifecycleReasonCodes = [...candidate.trigger.reasonCodes, `trigger:${triggerType}`];
139
+ emitLessonTelemetry({
140
+ event: 'learning_opportunity',
141
+ root,
142
+ payload,
143
+ sessionId,
144
+ candidateId,
145
+ observedAt,
146
+ status: 'success',
147
+ reasonCodes: lifecycleReasonCodes,
148
+ });
149
+ emitLessonTelemetry({
150
+ event: 'lesson_proposed',
151
+ root,
152
+ payload,
153
+ sessionId,
154
+ candidateId,
155
+ observedAt,
156
+ status: 'success',
157
+ reasonCodes: lifecycleReasonCodes,
158
+ });
137
159
  return { created: true, file, candidate };
138
160
  }
139
161
 
@@ -6,11 +6,26 @@ const path = require('path');
6
6
  const { getWorkspaceRoot, getStateRoot, getSessionDir } = require('./harness-state');
7
7
 
8
8
  const TELEMETRY_SCHEMA_VERSION = 1;
9
+ const LESSON_EVENT_TYPES = new Set([
10
+ 'learning_opportunity',
11
+ 'lesson_proposed',
12
+ 'lesson_screened',
13
+ 'lesson_evaluated',
14
+ 'lesson_accepted',
15
+ 'lesson_rejected',
16
+ 'lesson_inconclusive',
17
+ 'lesson_persisted',
18
+ 'lesson_retrieved',
19
+ 'lesson_validated',
20
+ 'lesson_regressed',
21
+ 'lesson_superseded',
22
+ ]);
9
23
  const EVENT_TYPES = new Set([
10
24
  'skill.invoke',
11
25
  'skill.loaded',
12
26
  'skill.complete',
13
27
  'tool.observed',
28
+ ...LESSON_EVENT_TYPES,
14
29
  ]);
15
30
  const STATUSES = new Set(['unknown', 'success', 'failure', 'aborted']);
16
31
  const FORBIDDEN_KEYS = new Set([
@@ -102,6 +117,29 @@ function telemetryFile(root, payload) {
102
117
  return path.join(getStateRoot(root || getWorkspaceRoot(payload), payload), 'telemetry', 'events.jsonl');
103
118
  }
104
119
 
120
+ function emitLessonTelemetry(input = {}) {
121
+ const event = String(input.event || '');
122
+ if (!LESSON_EVENT_TYPES.has(event)) {
123
+ return { ok: false, error: `unsupported lesson telemetry event: ${event}`, overheadMs: 0 };
124
+ }
125
+ const invocationId = localId('lesson', input.candidateId);
126
+ if (!invocationId) {
127
+ return { ok: false, error: 'lesson telemetry requires candidateId', overheadMs: 0 };
128
+ }
129
+ return emitTelemetry({
130
+ event,
131
+ host: ['claude', 'codex', 'opencode', 'unknown'].includes(input.host)
132
+ ? input.host
133
+ : detectHost(input.payload || {}),
134
+ observedAt: input.observedAt || nowIso(),
135
+ sessionId: localId('session', input.sessionId),
136
+ invocationId,
137
+ skillName: 'self-evolve',
138
+ status: STATUSES.has(input.status) ? input.status : 'unknown',
139
+ reasonCodes: input.reasonCodes || [],
140
+ }, { root: input.root, payload: input.payload });
141
+ }
142
+
105
143
  function emitTelemetry(input, options = {}) {
106
144
  const started = process.hrtime.bigint();
107
145
  try {
@@ -361,9 +399,11 @@ function readEvents(file) {
361
399
  module.exports = {
362
400
  EVENT_TYPES,
363
401
  FORBIDDEN_KEYS,
402
+ LESSON_EVENT_TYPES,
364
403
  STATUSES,
365
404
  TELEMETRY_SCHEMA_VERSION,
366
405
  detectHost,
406
+ emitLessonTelemetry,
367
407
  emitTelemetry,
368
408
  hostDuration,
369
409
  localId,
@@ -248,6 +248,35 @@ function mutationPaths(payload) {
248
248
  }
249
249
  return [input.file_path || input.filePath || input.path].filter(value => typeof value === 'string' && value.trim());
250
250
  }
251
+
252
+ // Bound workspace root plus its linked worktrees. Git failures (e.g. a
253
+ // non-git workspace) degrade to the root alone instead of throwing, so
254
+ // Tier-2 accounting never blocks on this enumeration (#165; cf. #162).
255
+ function worktreeRoots(root) {
256
+ const roots = [root];
257
+ try {
258
+ for (const field of git(root, ['worktree', 'list', '--porcelain', '-z']).split('\0')) {
259
+ if (field.startsWith('worktree ')) {
260
+ const candidate = field.slice('worktree '.length).trim();
261
+ if (candidate) roots.push(candidate);
262
+ }
263
+ }
264
+ } catch (_) { /* root alone */ }
265
+ return roots;
266
+ }
267
+
268
+ // Direct-tool (Edit/Write/apply_patch) targets count against the Tier-2
269
+ // budget only when at least one target lies inside the workspace (#165).
270
+ // Unknown targets (no parseable path) stay conservative and count.
271
+ function directMutationInWorkspace(payload, cwd, root) {
272
+ const targets = mutationPaths(payload);
273
+ if (!targets.length) return true;
274
+ const roots = worktreeRoots(root);
275
+ return targets.some(target => {
276
+ const resolved = path.resolve(cwd, target);
277
+ return roots.some(candidate => within(candidate, resolved));
278
+ });
279
+ }
251
280
  function assertTargets(payload, cwd, isolatedRoot) {
252
281
  const targets = mutationPaths(payload);
253
282
  if (!targets.length) throw new Error('mutation target is unknown; cannot verify worktree isolation');
@@ -274,4 +303,4 @@ function assertShellScope(command, cwd, isolatedRoot) {
274
303
  }
275
304
  }
276
305
 
277
- module.exports = { canonical, key, within, linkedWorktree, classifyShell, workspaceFingerprint, shellProbeKey, inputOf, cwdOf, commandOf, mutationPaths, assertTargets, assertShellScope };
306
+ module.exports = { canonical, key, within, linkedWorktree, classifyShell, workspaceFingerprint, shellProbeKey, inputOf, cwdOf, commandOf, mutationPaths, worktreeRoots, directMutationInWorkspace, assertTargets, assertShellScope };
@@ -162,6 +162,23 @@ function settleMutationProbe(context, key, afterFingerprint, tool) {
162
162
  });
163
163
  }
164
164
 
165
+ function settleMutationProbeConservative(context, key, tool, evidence = 'unobservable-workspace') {
166
+ return withMutationProbeLock(context, () => {
167
+ const file = mutationProbeFile(context, key);
168
+ const probe = readJson(file);
169
+ if (!probe || probe.schemaVersion !== 1) return null;
170
+ refreshWorkflowForProbe(context);
171
+ fs.unlinkSync(file);
172
+ context.workflow.lastMutationAt = Date.now();
173
+ if (probe.reserveIteration && context.workflow.strategy === 'iterative-single') {
174
+ recordBudgetEvent(context, 'iteration', { evidence: String(tool || 'shell') + ':' + evidence });
175
+ } else {
176
+ saveWorkflow(context);
177
+ }
178
+ return { changed: true, probe, conservative: true };
179
+ });
180
+ }
181
+
165
182
  function discardMutationProbe(context, key) {
166
183
  return withMutationProbeLock(context, () => {
167
184
  const file = mutationProbeFile(context, key);
@@ -396,4 +413,4 @@ function readHookInput(decide) {
396
413
  process.stdin.on('error', finish);
397
414
  }
398
415
 
399
- module.exports = { OPEN_STATES, SAFE_ID, WORKFLOW_CONTROLLER_COMMANDS, isMajorWorkflow, loadWorkflow, saveWorkflow, budgetLimits, ensureWorkflowBudget, mutationProbeReservations, assertIterationCapacity, registerMutationProbe, peekMutationProbe, settleMutationProbe, discardMutationProbe, clearMutationProbes, recordBudgetEvent, resetWorkflowBudget, syncBudgetToRun, matchingRun, unresolvedStages, readHookInput };
416
+ module.exports = { OPEN_STATES, SAFE_ID, WORKFLOW_CONTROLLER_COMMANDS, isMajorWorkflow, loadWorkflow, saveWorkflow, budgetLimits, ensureWorkflowBudget, mutationProbeReservations, assertIterationCapacity, registerMutationProbe, peekMutationProbe, settleMutationProbe, settleMutationProbeConservative, discardMutationProbe, clearMutationProbes, recordBudgetEvent, resetWorkflowBudget, syncBudgetToRun, matchingRun, unresolvedStages, readHookInput };
@@ -17,6 +17,7 @@ process.stdin.on('end', () => {
17
17
  const payload = JSON.parse(inputData);
18
18
  const hookEventName = payload.hook_event_name || payload.hookEventName || '';
19
19
  const isFailureEvent = hookEventName === 'PostToolUseFailure';
20
+ const isSuccessEvent = hookEventName === 'PostToolUse';
20
21
  const toolResponse = payload.tool_response || {};
21
22
  const stdout = toolResponse.stdout ?? payload.stdout ?? '';
22
23
  const stderr = toolResponse.stderr ?? payload.stderr ?? '';
@@ -25,10 +26,10 @@ process.stdin.on('end', () => {
25
26
  const exitCode = typeof rawExitCode === 'number' ? rawExitCode : undefined;
26
27
 
27
28
  const explicitFailure = exitCode !== undefined && exitCode !== 0;
28
- const explicitSuccess = exitCode === 0;
29
29
  const stderrSignal = typeof stderr === 'string' && stderr.trim().length > 0;
30
30
  const looksLikeError = /\b(error|fail|failed|failure|exception|fatal|panic|traceback|denied|refused|cannot|unable)\b/i.test(stderr);
31
- const isFailure = isFailureEvent || explicitFailure || (exitCode === undefined && stderrSignal && looksLikeError);
31
+ const isFailure = isFailureEvent || explicitFailure ||
32
+ (!isSuccessEvent && exitCode === undefined && stderrSignal && looksLikeError);
32
33
  const errorText = isFailureEvent
33
34
  ? (failureText || (stderrSignal ? stderr : stdout) || '')
34
35
  : ((stderrSignal ? stderr : stdout) || failureText || '');
@@ -47,19 +48,22 @@ process.stdin.on('end', () => {
47
48
  const filePath = payload.tool_input?.file_path || payload.tool_input?.filePath || '';
48
49
  const command = payload.tool_input?.command || '';
49
50
 
50
- // Categorize failure type for context-aware thresholds
51
+ // Categorize failure type for context-aware thresholds.
52
+ // NOTE (#167): match anchored diagnostic phrases, not bare substrings.
53
+ // A bare `denied`/`timeout` substring matches file names such as
54
+ // `workflow-mutation-denied.js` and misclassifies the failure.
51
55
  let category = 'unknown';
52
- if (/SyntaxError|Unexpected token|Syntax Error/i.test(errorText)) {
56
+ if (/\bSyntaxError\b|Unexpected token|Syntax Error/i.test(errorText)) {
53
57
  category = 'syntax';
54
- } else if (/ENOENT|not found|No such file|找不到|不存在/i.test(errorText)) {
58
+ } else if (/\bENOENT\b|\bnot found\b|No such file|找不到|不存在/i.test(errorText)) {
55
59
  category = 'environment';
56
- } else if (/timeout|timed out|超時|逾時/i.test(errorText)) {
60
+ } else if (/\bETIMEDOUT\b|TimeoutError|\btimeout\b|\btimed out\b|\btiming out\b|超時|逾時/i.test(errorText)) {
57
61
  category = 'timeout';
58
62
  } else if (/test.*fail|assertion|AssertionError|測試.*失敗|斷言.*錯誤/i.test(errorText)) {
59
63
  category = 'test';
60
- } else if (/permission|denied|EACCES|權限/i.test(errorText)) {
64
+ } else if (/\bpermission denied\b|\bEACCES\b|\bEPERM\b|權限.*(拒絕|不足|被拒)|拒絕.*權限/i.test(errorText)) {
61
65
  category = 'permission';
62
- } else if (/dependency|module not found|Cannot find module|模組.*找不到/i.test(errorText)) {
66
+ } else if (/\bdependency\b|\bmodule not found\b|Cannot find module|模組.*找不到/i.test(errorText)) {
63
67
  category = 'dependency';
64
68
  }
65
69
 
@@ -114,7 +118,11 @@ process.stdin.on('end', () => {
114
118
  state.lastFailureAt = Date.now();
115
119
 
116
120
  fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8');
117
- } else if (!isFailureEvent && explicitSuccess) {
121
+ } else if (!isFailure) {
122
+ // NOTE (#166, follows #153): Claude Code PostToolUse success payloads
123
+ // carry no numeric exit code. The lifecycle event is authoritative:
124
+ // PostToolUse is success unless a numeric non-zero status contradicts it;
125
+ // hosts without lifecycle event names keep the conservative stderr fallback.
118
126
  // A successful action after an accepted zoom-out is an objective recovery
119
127
  // boundary. Emit only IDs/hashes/category; never copy command/output text.
120
128
  if ((state.zoomOutCycles || 0) > 0 && state.lastHash) {