@manhd_d/ai-harness 0.12.0 → 0.14.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 (144) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +28 -37
  3. package/adapters/ai-python/adapter.yaml +1 -1
  4. package/adapters/ai-python/rules.md +3 -0
  5. package/adapters/ai-python/skills/harness-ai-python-rag-access/SKILL.md +1 -0
  6. package/adapters/ai-python/skills/harness-ai-python-tool-safety/SKILL.md +1 -0
  7. package/adapters/astro/adapter.yaml +3 -5
  8. package/adapters/astro/checks/verification.yaml +6 -6
  9. package/adapters/astro/evals/cases.json +5 -5
  10. package/adapters/astro/rules/architecture.md +2 -8
  11. package/adapters/astro/rules/deployment.md +2 -8
  12. package/adapters/astro/rules.md +5 -14
  13. package/adapters/astro/skills/harness-astro-islands/SKILL.md +3 -2
  14. package/adapters/astro/skills/harness-astro-performance/SKILL.md +1 -0
  15. package/adapters/astro/skills/harness-astro-project-architecture/SKILL.md +9 -19
  16. package/adapters/astro/skills/harness-astro-seo/SKILL.md +1 -0
  17. package/adapters/java-spring/adapter.yaml +2 -4
  18. package/adapters/java-spring/rules/database.md +2 -2
  19. package/adapters/java-spring/rules/performance.md +2 -2
  20. package/adapters/java-spring/rules.md +4 -1
  21. package/adapters/java-spring/skills/harness-java-spring-spring-api/SKILL.md +1 -0
  22. package/adapters/kubernetes/adapter.yaml +1 -1
  23. package/adapters/kubernetes/rules.md +3 -0
  24. package/adapters/kubernetes/skills/harness-kubernetes-helm-release/SKILL.md +1 -0
  25. package/adapters/kubernetes/skills/harness-kubernetes-rollout-safety/SKILL.md +1 -0
  26. package/adapters/kubernetes/skills/harness-kubernetes-workload-review/SKILL.md +1 -0
  27. package/adapters/react-native/adapter.yaml +1 -1
  28. package/adapters/react-native/rules.md +3 -0
  29. package/adapters/react-native/skills/harness-react-native-behavior-testing/SKILL.md +1 -0
  30. package/adapters/react-native/skills/harness-react-native-device-permissions/SKILL.md +1 -0
  31. package/adapters/react-native/skills/harness-react-native-offline-sync/SKILL.md +1 -0
  32. package/adapters/react-native/skills/harness-react-native-screen-delivery/SKILL.md +1 -0
  33. package/adapters/react-nextjs/adapter.yaml +1 -1
  34. package/adapters/react-nextjs/rules.md +3 -0
  35. package/adapters/react-nextjs/skills/harness-react-nextjs-accessible-ui/SKILL.md +1 -0
  36. package/adapters/react-nextjs/skills/harness-react-nextjs-behavior-testing/SKILL.md +1 -0
  37. package/adapters/react-nextjs/skills/harness-react-nextjs-feature-delivery/SKILL.md +1 -0
  38. package/adapters/react-nextjs/skills/harness-react-nextjs-server-boundaries/SKILL.md +1 -0
  39. package/bin/aih.js +1 -1
  40. package/capabilities/analysis/capability.json +48 -0
  41. package/capabilities/analysis/checks/verification.json +16 -0
  42. package/capabilities/analysis/evals/cases.json +15 -0
  43. package/capabilities/architecture/agents/architecture-reviewer.md +5 -0
  44. package/capabilities/architecture/agents/solution-architect.md +5 -0
  45. package/capabilities/architecture/capability.json +1 -1
  46. package/capabilities/delivery/agents/project-manager.md +10 -11
  47. package/capabilities/delivery/capability.json +8 -2
  48. package/capabilities/delivery/templates/delivery-plan.md +2 -23
  49. package/capabilities/discovery/capability.json +37 -0
  50. package/capabilities/discovery/checks/verification.json +16 -0
  51. package/capabilities/discovery/evals/cases.json +15 -0
  52. package/capabilities/product/agents/business-analyst.md +5 -0
  53. package/capabilities/product/agents/product-manager.md +5 -0
  54. package/capabilities/product/agents/product-owner.md +5 -0
  55. package/capabilities/product/agents/project-manager.md +9 -14
  56. package/capabilities/product/agents/requirements-reviewer.md +5 -0
  57. package/capabilities/product/capability.json +8 -2
  58. package/capabilities/product/common/document-contract.md +10 -0
  59. package/capabilities/product/evals/cases.json +11 -0
  60. package/capabilities/product/skills/harness-product-acceptance-criteria/SKILL.md +4 -7
  61. package/capabilities/product/skills/harness-product-backlog-prioritization/SKILL.md +2 -6
  62. package/capabilities/product/skills/harness-product-brd-generation/SKILL.md +2 -6
  63. package/capabilities/product/skills/harness-product-business-analysis/SKILL.md +7 -10
  64. package/capabilities/product/skills/harness-product-document-review/SKILL.md +2 -6
  65. package/capabilities/product/skills/harness-product-idea-discovery/SKILL.md +2 -6
  66. package/capabilities/product/skills/harness-product-knowledge-analysis/SKILL.md +2 -6
  67. package/capabilities/product/skills/harness-product-mvp-definition/SKILL.md +1 -2
  68. package/capabilities/product/skills/harness-product-prd-generation/SKILL.md +2 -6
  69. package/capabilities/product/skills/harness-product-product-outcomes/SKILL.md +1 -2
  70. package/capabilities/product/skills/harness-product-product-research/SKILL.md +2 -6
  71. package/capabilities/product/skills/harness-product-srs-generation/SKILL.md +2 -6
  72. package/capabilities/product/skills/harness-product-techspec/SKILL.md +1 -1
  73. package/capabilities/product/skills/harness-product-traceability/SKILL.md +2 -6
  74. package/capabilities/product/skills/harness-product-use-case-generation/SKILL.md +2 -6
  75. package/capabilities/product/skills/harness-product-user-stories/SKILL.md +2 -6
  76. package/capabilities/product/templates/delivery-plan.md +3 -17
  77. package/cli/adapters.js +9 -5
  78. package/cli/agents.js +45 -9
  79. package/cli/capabilities.js +39 -8
  80. package/cli/checkpoint.js +57 -0
  81. package/cli/claude.js +40 -0
  82. package/cli/comparison.js +46 -0
  83. package/cli/composition.js +1 -0
  84. package/cli/files.js +2 -1
  85. package/cli/harness.js +78 -20
  86. package/cli/main.js +51 -15
  87. package/cli/maven.js +28 -0
  88. package/cli/portable-config.js +121 -0
  89. package/cli/start-config.js +15 -0
  90. package/cli/start.js +138 -0
  91. package/cli/verification-report.js +1 -1
  92. package/config/agents.claude-code.json +100 -0
  93. package/config/agents.default.json +25 -5
  94. package/core/agents/documentation.md +5 -0
  95. package/core/agents/implementer.md +5 -0
  96. package/core/agents/orchestrator.md +5 -0
  97. package/core/agents/planner.md +5 -0
  98. package/core/agents/project-manager.md +20 -0
  99. package/core/agents/reviewer.md +5 -0
  100. package/core/agents/security-reviewer.md +5 -0
  101. package/core/agents/test-engineer.md +5 -0
  102. package/core/checks/verification.json +12 -0
  103. package/core/evals/cases.json +44 -0
  104. package/core/policies/permissions.md +3 -1
  105. package/core/workflows/agent-routing.md +27 -26
  106. package/core/workflows/continuous-development.md +3 -0
  107. package/core/workflows/project-setup.md +18 -0
  108. package/core/workflows/spec-to-code.md +1 -1
  109. package/docs/README.md +6 -1
  110. package/docs/agent-configuration.md +9 -5
  111. package/docs/agent-first.md +66 -0
  112. package/docs/agent-handoffs.md +4 -0
  113. package/docs/architecture.md +14 -0
  114. package/docs/cli.md +21 -0
  115. package/docs/configuration-backup.md +122 -0
  116. package/docs/continuous-development.md +4 -0
  117. package/docs/examples/astro-micro-tools.md +1 -1
  118. package/docs/examples/checkpoint.json +20 -0
  119. package/docs/examples/spring-boot-api.md +2 -4
  120. package/docs/getting-started.md +9 -6
  121. package/docs/i18n/README.es.md +35 -0
  122. package/docs/i18n/README.vi.md +21 -43
  123. package/docs/i18n/README.zh-CN.md +35 -0
  124. package/docs/project-configuration.md +9 -1
  125. package/docs/skill-matrix.md +7 -7
  126. package/docs/start.md +38 -0
  127. package/docs/tool-setup/README.md +69 -67
  128. package/docs/tool-setup/claude-code.md +41 -49
  129. package/docs/tool-setup/codex.md +31 -43
  130. package/docs/tool-setup/copilot.md +35 -38
  131. package/docs/tool-setup/cursor.md +25 -40
  132. package/docs/tool-setup/gemini-cli.md +31 -35
  133. package/docs/tool-setup/kiro.md +55 -0
  134. package/docs/versioning.md +24 -0
  135. package/evals/real-task-comparison.md +4 -0
  136. package/harness.yaml +4 -2
  137. package/package.json +3 -2
  138. package/scripts/compare-tasks.js +15 -0
  139. package/templates/AGENTS.md +20 -28
  140. package/templates/delivery-plan.md +25 -0
  141. package/adapters/astro/skills/harness-astro-cloudflare/SKILL.md +0 -27
  142. package/adapters/astro/skills/harness-astro-microfrontend/SKILL.md +0 -27
  143. package/adapters/java-spring/skills/harness-java-spring-database-migration/SKILL.md +0 -23
  144. package/adapters/java-spring/skills/harness-java-spring-kafka-outbox/SKILL.md +0 -23
@@ -32,8 +32,7 @@ For customer lookup, propose task completion and observed latency as measures. W
32
32
  - Experiment does not silently expand collection or release scope
33
33
 
34
34
  ## Handoff
35
- State source revisions, requirement/decision IDs, approval status, unresolved questions, output paths,
36
- actual checks and next role. Use the module workflow; role names are not human authorization.
35
+ Read the shared installed contract `.harness/capabilities/product/common/document-contract.md`; pass only affected scope and relevant perspectives.
37
36
 
38
37
  ## Constraints
39
38
  Follow Core permissions, clarification and project-conventions. Ask only consequential unknowns with
@@ -28,17 +28,13 @@ description: Research comparable products, user needs and implementation feasibi
28
28
 
29
29
  ## Template
30
30
  Read `.harness/capabilities/product/templates/research.md`; `aih new-doc research` creates only a missing
31
- draft. Reuse canonical existing documents and source pointers; never regenerate approved content by default.
31
+ draft. Apply the shared document contract for authority, preservation and review.
32
32
 
33
33
  ## Example
34
34
  Synthetic illustration: With no browsing access, competitor claims are UNKNOWN and research questions are proposed rather than citations invented.
35
35
 
36
36
  ## Quality checklist
37
37
  - Retrieved facts, inferences and forecasts have provenance.
38
- - Actual decisions/status and source revisions distinguish approved facts from proposals.
39
- - Scope, relevant IDs, unresolved questions and verification limits are recorded.
40
38
 
41
39
  ## Handoff
42
- Use `.harness/capabilities/product/workflows/product-to-solution.md`; supply reviewed source/requirement
43
- IDs, output paths and actual owner decisions to the next BA/PO/SA/PM role as applicable. Return gaps
44
- to their owner; a single host may perform roles sequentially without claiming independent approval.
40
+ Read the shared installed contract `.harness/capabilities/product/common/document-contract.md`; pass only affected scope and relevant perspectives.
@@ -29,17 +29,13 @@ description: Produce a testable Software Requirements Specification linked to ap
29
29
 
30
30
  ## Template
31
31
  Read `.harness/capabilities/product/templates/srs.md`; `aih new-doc srs` creates only a missing
32
- draft. Reuse canonical existing documents and source pointers; never regenerate approved content by default.
32
+ draft. Apply the shared document contract for authority, preservation and review.
33
33
 
34
34
  ## Example
35
35
  Synthetic illustration: FR-001 defines search behavior; NFR-001 target is pending until agreed and is passed to Architecture for a scenario/test plan.
36
36
 
37
37
  ## Quality checklist
38
38
  - FR/NFR contracts, exception paths and trace IDs remain consistent.
39
- - Actual decisions/status and source revisions distinguish approved facts from proposals.
40
- - Scope, relevant IDs, unresolved questions and verification limits are recorded.
41
39
 
42
40
  ## Handoff
43
- Use `.harness/capabilities/product/workflows/product-to-solution.md`; supply reviewed source/requirement
44
- IDs, output paths and actual owner decisions to the next BA/PO/SA/PM role as applicable. Return gaps
45
- to their owner; a single host may perform roles sequentially without claiming independent approval.
41
+ Read the shared installed contract `.harness/capabilities/product/common/document-contract.md`; pass only affected scope and relevant perspectives.
@@ -32,4 +32,4 @@ Idea: a static calculator needs UX/formula/build/accessibility evidence, no API
32
32
  Source revisions, requirement IDs, contracts, actual execution evidence and limits are distinguishable. Partial updates preserve approved text.
33
33
 
34
34
  ## Handoff
35
- Use `.harness/capabilities/product/workflows/techspec-maintenance.md`; pass evidence and proposed changes to review. Documentation remains pending-review until accepted.
35
+ Read the shared installed contract `.harness/capabilities/product/common/document-contract.md`; pass only affected scope and relevant perspectives.
@@ -25,20 +25,16 @@ description: Maintain bidirectional links from document sources through BRD/PRD/
25
25
 
26
26
  ## Template
27
27
  Read `.harness/capabilities/product/templates/traceability.md`; `aih new-doc traceability` creates only a missing
28
- draft. Reuse canonical existing documents and source pointers; never regenerate approved content by default.
28
+ draft. Apply the shared document contract for authority, preservation and review.
29
29
 
30
30
  ## Example
31
31
  Synthetic illustration: SRC-001 → FR-001 → US-001 → AC-US-001-01 → ADR-001/OpenSpec task/test links form a checkable chain.
32
32
 
33
33
  ## Quality checklist
34
34
  - Stable IDs, source versions and downstream gaps are maintained.
35
- - Actual decisions/status and source revisions distinguish approved facts from proposals.
36
- - Scope, relevant IDs, unresolved questions and verification limits are recorded.
37
35
 
38
36
  ## Handoff
39
- Use `.harness/capabilities/product/workflows/product-to-solution.md`; supply reviewed source/requirement
40
- IDs, output paths and actual owner decisions to the next BA/PO/SA/PM role as applicable. Return gaps
41
- to their owner; a single host may perform roles sequentially without claiming independent approval.
37
+ Read the shared installed contract `.harness/capabilities/product/common/document-contract.md`; pass only affected scope and relevant perspectives.
42
38
 
43
39
  ## Continuous changes
44
40
  Extend the same matrix through scoped code/contracts and tests/raw evidence with their revisions.
@@ -27,17 +27,13 @@ description: Translate requirement and story IDs into consistent main, alternati
27
27
 
28
28
  ## Template
29
29
  Read `.harness/capabilities/product/templates/use-cases.md`; `aih new-doc use-cases` creates only a missing
30
- draft. Reuse canonical existing documents and source pointers; never regenerate approved content by default.
30
+ draft. Apply the shared document contract for authority, preservation and review.
31
31
 
32
32
  ## Example
33
33
  Synthetic illustration: UC-001 contains main lookup, no-result, permission denial and provider timeout paths linked to FR/AC.
34
34
 
35
35
  ## Quality checklist
36
36
  - Actor, precondition, result and alternate/error flows are explicit.
37
- - Actual decisions/status and source revisions distinguish approved facts from proposals.
38
- - Scope, relevant IDs, unresolved questions and verification limits are recorded.
39
37
 
40
38
  ## Handoff
41
- Use `.harness/capabilities/product/workflows/product-to-solution.md`; supply reviewed source/requirement
42
- IDs, output paths and actual owner decisions to the next BA/PO/SA/PM role as applicable. Return gaps
43
- to their owner; a single host may perform roles sequentially without claiming independent approval.
39
+ Read the shared installed contract `.harness/capabilities/product/common/document-contract.md`; pass only affected scope and relevant perspectives.
@@ -26,17 +26,13 @@ description: Turn approved requirements into atomic epics and user stories with
26
26
 
27
27
  ## Template
28
28
  Read `.harness/capabilities/product/templates/backlog.md`; `aih new-doc backlog` creates only a missing
29
- draft. Reuse canonical existing documents and source pointers; never regenerate approved content by default.
29
+ draft. Apply the shared document contract for authority, preservation and review.
30
30
 
31
31
  ## Example
32
32
  Synthetic illustration: US-001: authorized employee finds a customer; links FR-001 and AC-US-001-01, with access-denial criteria.
33
33
 
34
34
  ## Quality checklist
35
35
  - Story has user value, dependency and observable acceptance.
36
- - Actual decisions/status and source revisions distinguish approved facts from proposals.
37
- - Scope, relevant IDs, unresolved questions and verification limits are recorded.
38
36
 
39
37
  ## Handoff
40
- Use `.harness/capabilities/product/workflows/product-to-solution.md`; supply reviewed source/requirement
41
- IDs, output paths and actual owner decisions to the next BA/PO/SA/PM role as applicable. Return gaps
42
- to their owner; a single host may perform roles sequentially without claiming independent approval.
38
+ Read the shared installed contract `.harness/capabilities/product/common/document-contract.md`; pass only affected scope and relevant perspectives.
@@ -1,18 +1,4 @@
1
- # Delivery plan and RAID register (optional)
1
+ # Delivery plan — compatibility template
2
2
 
3
- Status: draft | PM: <person> | Planning horizon: <dates or TBD>
4
-
5
- ## Milestones
6
- | Milestone | Scope / deliverables | Dependency | Estimate evidence | Proposed date | Accountable owner |
7
- | --- | --- | --- | --- | --- | --- |
8
-
9
- ## RAID log
10
- | ID | Risk / Assumption / Issue / Dependency | Probability / impact (if evidenced) | Mitigation | Owner | Status |
11
- | --- | --- | --- | --- | --- | --- |
12
-
13
- ## Communications / change control
14
- - Stakeholders and review cadence:
15
- - Scope change requests and PRD/BRD impact:
16
- - Escalation route:
17
-
18
- Never substitute invented effort, capacity or promised dates for team estimates.
3
+ Status: draft / pending review
4
+ Canonical template: .harness/templates/delivery-plan.md. Existing document paths remain project-owned; new-doc resolves this alias without migrating old documents.
package/cli/adapters.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import { mavenModules } from './maven.js';
2
3
  import path from 'node:path';
3
4
  import { safe, readJson, manifest, HarnessError, check, loadLock, encode, digest } from './harness.js';
4
5
  import { scoreDataset } from '../scripts/score-eval.js';
@@ -23,11 +24,13 @@ function probe(root, file) {
23
24
  return fs.readFileSync(filename, 'utf8');
24
25
  }
25
26
  export function detect(root, source) {
26
- const matches = [];
27
+ const matches = [], maven = mavenModules(root);
27
28
  for (const id of manifest(source).adapters) {
28
29
  const a = loadAdapter(source, id), evidence = [];
29
30
  for (const rule of a.detection) {
30
- const body = probe(root, rule.file);
31
+ const probes = rule.file === 'pom.xml' ? maven.files : [{ file: rule.file, body: probe(root, rule.file) }];
32
+ for (const entry of probes) {
33
+ const body = entry.body;
31
34
  if (body === null) continue;
32
35
  let found;
33
36
  if (rule.kind === 'dependencies') {
@@ -41,7 +44,8 @@ export function detect(root, source) {
41
44
  }
42
45
  found = rule.values.filter(v => deps.has(v));
43
46
  } else found = rule.values.filter(v => body.includes(v));
44
- if (found.length) evidence.push({ file: rule.file, kind: rule.kind, matched: found });
47
+ if (found.length) evidence.push({ file: entry.file, kind: rule.kind, matched: found });
48
+ }
45
49
  }
46
50
  if (evidence.length) matches.push({ id, evidence });
47
51
  }
@@ -51,8 +55,8 @@ export function detect(root, source) {
51
55
  const astroDetected = matches.some(match => match.id === 'astro');
52
56
  const filtered = matches.filter(match => !(astroDetected && match.id === 'react-nextjs'
53
57
  && !match.evidence.some(row => row.matched.includes('next'))));
54
- return { status: 'ok', adapters: filtered.map(m => m.id).sort(), matches: filtered,
55
- note: 'Heuristic, current target only; review evidence. No recursive monorepo scan or application execution.' };
58
+ return { status: 'ok', adapters: filtered.map(m => m.id).sort(), matches: filtered, warnings: maven.warnings,
59
+ note: 'Heuristic metadata only; bounded declared Maven modules (depth 2, 64 POMs, 4 MiB total). No application execution; other monorepos require scoped host discovery.' };
56
60
  }
57
61
 
58
62
  function installedAdapters(root) {
package/cli/agents.js CHANGED
@@ -61,7 +61,7 @@ export function validateAgentConfig(config) {
61
61
  requireThat(agent.enabled === undefined || typeof agent.enabled === 'boolean', id + ' enabled');
62
62
  }
63
63
  const execution = config.execution;
64
- fields(execution, ['mode', 'maxConcurrentAgents', 'maxRounds', 'autoEscalateToPremium'],
64
+ fields(execution, ['mode', 'maxConcurrentAgents', 'maxRounds', 'autoEscalateToPremium', 'autonomy', 'budget'],
65
65
  ['mode', 'maxConcurrentAgents', 'maxRounds', 'autoEscalateToPremium'], 'execution');
66
66
  requireThat(['single-agent', 'hybrid', 'multi-agent'].includes(execution.mode), 'execution mode');
67
67
  requireThat(Number.isSafeInteger(execution.maxConcurrentAgents) && execution.maxConcurrentAgents >= 1
@@ -70,6 +70,21 @@ export function validateAgentConfig(config) {
70
70
  requireThat(Number.isSafeInteger(execution.maxRounds) && execution.maxRounds >= 1 && execution.maxRounds <= 20,
71
71
  'maxRounds must be an integer from 1 to 20');
72
72
  requireThat(execution.autoEscalateToPremium === false, 'autoEscalateToPremium must be false; select premium explicitly');
73
+ if (execution.autonomy !== undefined) {
74
+ const a=execution.autonomy;
75
+ fields(a,['mode','actions','repositories'],['mode','actions','repositories'],'autonomy');
76
+ requireThat(['assisted','autopilot'].includes(a.mode),'autonomy mode');
77
+ const allowed=['read','edit','test','commit','push','create-pr','source-write','merge','release','deploy'];
78
+ requireThat(Array.isArray(a.actions)&&new Set(a.actions).size===a.actions.length&&a.actions.every(v=>allowed.includes(v)),'autonomy actions');
79
+ requireThat(Array.isArray(a.repositories)&&new Set(a.repositories).size===a.repositories.length&&a.repositories.every(v=>text(v)&&/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(v)),'autonomy repositories');
80
+ requireThat(a.mode!=='autopilot'||(a.actions.length>0&&a.repositories.length>0),'autopilot needs explicit actions and repository scope');
81
+ }
82
+ if (execution.budget !== undefined) {
83
+ const b=execution.budget;fields(b,['maxTokens','maxCost','currency'],[],'budget');
84
+ requireThat(b.maxTokens===undefined||(Number.isSafeInteger(b.maxTokens)&&b.maxTokens>0),'maxTokens');
85
+ requireThat(b.maxCost===undefined||(Number.isFinite(b.maxCost)&&b.maxCost>=0&&text(b.currency)),'maxCost/currency');
86
+ requireThat(b.maxTokens!==undefined||b.maxCost!==undefined,'budget needs a declared limit');
87
+ }
73
88
  const hosts = config.hosts ?? {};
74
89
  requireThat(isObject(hosts) && Object.keys(hosts).length <= 16, 'hosts map (max 16 profiles)');
75
90
  for (const [id, profile] of Object.entries(hosts)) {
@@ -97,10 +112,11 @@ function readConfiguration(root, relative) {
97
112
  }
98
113
  export function loadAgentConfig(root) { return readConfiguration(root, AGENT_CONFIG); }
99
114
 
100
- export function agentDefaults(source = SOURCE, preset = 'recommended') {
115
+ export function agentDefaults(source = SOURCE, preset = 'recommended', host) {
101
116
  requireThat(PRESETS.includes(preset), 'unknown preset; use recommended, economy or custom');
102
117
  // Older alternate bundles remain usable; the packaged CLI supplies the new canonical seed.
103
- const config = structuredClone(readConfiguration(source, DEFAULT_AGENTS) ?? readConfiguration(SOURCE, DEFAULT_AGENTS));
118
+ const seed = host === 'claude-code' ? 'config/agents.claude-code.json' : DEFAULT_AGENTS;
119
+ const config = structuredClone(readConfiguration(source, seed) ?? readConfiguration(SOURCE, seed));
104
120
  requireThat(config !== null, 'missing canonical default');
105
121
  if (preset === 'economy') {
106
122
  config.defaultModel = '@economy'; config.defaultReasoning = 'low';
@@ -120,7 +136,7 @@ export function registeredRoles(source, capabilities = []) {
120
136
  const metadata = readJson(safe(source, `capabilities/${id}/capability.json`));
121
137
  for (const file of metadata.agents ?? []) {
122
138
  const role = id + ':' + path.basename(file, '.md');
123
- roles[role] = `capabilities/${id}/${file}`;
139
+ roles[role] = metadata.agent_aliases?.[file] ?? `capabilities/${metadata.asset_owner ?? id}/${file}`;
124
140
  }
125
141
  }
126
142
  return roles;
@@ -137,10 +153,10 @@ export function resolveAgent(config, id) {
137
153
  export function agentPlan(root, host) {
138
154
  if (host !== undefined) requireThat(typeof host === 'string' && SLUG.test(host), 'host ID');
139
155
  const config = loadAgentConfig(root);
140
- const presets = [{ label: 'A', value: 'recommended', description: 'Sol, hybrid, at most two concurrent agents when supported.' },
156
+ const presets = [{ label: 'A', value: 'recommended', description: 'Keep the host-selected model; hybrid, at most two concurrent agents when supported.' },
141
157
  { label: 'B', value: 'economy', description: 'Economy model, single-agent roles.' },
142
158
  { label: 'C', value: 'custom', description: 'Edit provider/model/reasoning and role/host mappings explicitly.' }];
143
- const note = 'Read-only declaration. No provider/model probing, native config writes, agent spawning, concurrency/token enforcement or usage tracking. Verify model access, effort and delegation in the actual host before execution.';
159
+ const note = 'Read-only declaration. No provider/model probing, native config writes, agent spawning, permission/concurrency/token enforcement or usage tracking. Autonomy and budget are declarations; verify actual user authorization and host limits. Verify model access, effort and delegation in the actual host before execution.';
144
160
  if (!config) return { status: 'not-configured', configured: false, presets, roles: [], note };
145
161
  const lock = readJson(safe(root, '.harness/lock.json'));
146
162
  const registry = registeredRoles(path.join(root, '.harness'), lock.capabilities ?? []);
@@ -151,9 +167,12 @@ export function agentPlan(root, host) {
151
167
  const active = requested.enabled && Object.hasOwn(registry, id);
152
168
  const mapping = profile.models?.[requested.provider + '/' + requested.model];
153
169
  const incompatible = !mapping && ((host === 'claude-code' &&
154
- (!['anthropic', 'bedrock', 'vertex'].includes(requested.provider) || requested.model.startsWith('gpt-'))) ||
155
- (host === 'gemini-cli' && (!['google', 'vertex'].includes(requested.provider) || requested.model.startsWith('gpt-'))));
156
- return { ...requested, active, contract: registry[id] ? '.harness/' + registry[id] : null,
170
+ (!['host', 'anthropic', 'bedrock', 'vertex'].includes(requested.provider) || requested.model.startsWith('gpt-'))) ||
171
+ (host === 'gemini-cli' && (!['host', 'google', 'vertex'].includes(requested.provider) || requested.model.startsWith('gpt-'))));
172
+ const parts = registry[id]?.split('/');
173
+ const canonical = parts ? (parts[0] === 'core' ? 'core:' : parts[1] + ':') + path.basename(registry[id], '.md') : id;
174
+ const skills = registry[id] ? preferredSkills(fs.readFileSync(safe(path.join(root, '.harness'), registry[id]), 'utf8')) : { required: [], optional: [] };
175
+ return { ...requested, active, canonical_id: canonical, skills, contract: registry[id] ? '.harness/' + registry[id] : null,
157
176
  host_model: mapping ?? requested.model, host_reasoning: profile.reasoning?.[requested.reasoning] ?? requested.reasoning,
158
177
  mapping: mapping ? 'explicit-user-host-mapping' : 'requested',
159
178
  availability: active && incompatible ? 'incompatible-provider' : 'unverified',
@@ -163,3 +182,20 @@ export function agentPlan(root, host) {
163
182
  configured: true, host: host ?? null, presets, execution: config.execution, roles,
164
183
  missing_mappings: Object.keys(registry).filter(id => !Object.hasOwn(config.agents, id)), note };
165
184
  }
185
+
186
+ export function preferredSkills(body) {
187
+ const result={required:[],optional:[]};
188
+ const section=body.split('## Preferred skills')[1]?.split(/\n## /)[0] ?? '';
189
+ for(const line of section.split(/\r?\n/)){
190
+ const kind=line.match(/^- (Required|Optional):/)?.[1]?.toLowerCase();if(!kind)continue;
191
+ result[kind].push(...[...line.matchAll(/harness-[a-z0-9-]+/g)].map(m=>m[0]));
192
+ }
193
+ for(const list of Object.values(result))if(new Set(list).size!==list.length)throw new HarnessError('Duplicate preferred skill');
194
+ return result;
195
+ }
196
+ export function validatePreferredSkills(body, known) {
197
+ if(!body.includes('## Preferred skills'))throw new HarnessError('Agent missing preferred skill declaration');
198
+ const declared=preferredSkills(body);
199
+ for(const name of [...declared.required,...declared.optional])if(!known.has(name))throw new HarnessError('Unknown preferred skill: '+name);
200
+ return declared;
201
+ }
@@ -26,22 +26,44 @@ export function loadCapability(source, id) {
26
26
  const meta = readJson(safe(source, prefix + 'capability.json'));
27
27
  // Schema 1 supports optional role and template lists; legacy UI/UX stays valid.
28
28
  const required = ['schema_version', 'id', 'description', 'skills', 'workflows'];
29
- const allowed = new Set([...required, 'agents', 'templates', 'module_version', 'requires', 'recommends', 'conflicts', 'compatible_adapters', 'verification', 'evaluation']);
29
+ const allowed = new Set([...required, 'agents', 'templates', 'module_version', 'requires', 'recommends', 'conflicts', 'compatible_adapters', 'verification', 'evaluation', 'asset_owner', 'template_aliases', 'agent_aliases']);
30
30
  if (!meta || typeof meta !== 'object' || Array.isArray(meta)
31
31
  || required.some(k => !Object.hasOwn(meta, k))
32
32
  || Object.keys(meta).some(k => !allowed.has(k))
33
33
  || meta.schema_version !== 1 || meta.id !== id
34
34
  || typeof meta.description !== 'string' || !meta.description.trim()) fail(`Invalid capability: ${id}`);
35
35
  validateContract(meta, 'capability', id);
36
+ const owner = meta.asset_owner ?? id;
37
+ if (typeof owner !== 'string' || !/^[a-z][a-z0-9-]*$/.test(owner)) fail('Invalid capability asset owner');
38
+ const assetPrefix = 'capabilities/' + owner + '/';
39
+ if (owner !== id) {
40
+ const ownerMeta = readJson(safe(source, assetPrefix + 'capability.json'));
41
+ if (ownerMeta?.asset_owner) fail('Capability profiles require a canonical owner');
42
+ const canonical = loadCapability(source, owner);
43
+ for (const key of ['skills','workflows','agents','templates']) {
44
+ if (!Array.isArray(meta[key] ?? []) || !(meta[key] ?? []).every(v => (canonical[key] ?? []).includes(v))) fail('Profile contains undeclared canonical ' + key);
45
+ }
46
+ }
47
+ for (const [field, list, pattern] of [
48
+ ['template_aliases', meta.templates ?? [], /^templates\/[a-z][a-z0-9-]*[.]md$/],
49
+ ['agent_aliases', meta.agents ?? [], /^core\/agents\/[a-z][a-z0-9-]*[.]md$/],
50
+ ]) {
51
+ const aliases = meta[field] ?? {};
52
+ if (!aliases || typeof aliases !== 'object' || Array.isArray(aliases)) fail('Invalid capability aliases');
53
+ for (const [from, to] of Object.entries(aliases)) {
54
+ if (!list.includes(from) || typeof to !== 'string' || !pattern.test(to)) fail('Invalid capability alias');
55
+ if (!fs.readFileSync(safe(source, to), 'utf8').trim()) fail('Empty canonical alias');
56
+ }
57
+ }
36
58
  if (!Array.isArray(meta.skills) || !meta.skills.length || !unique(meta.skills)
37
- || !meta.skills.every(name => validName(name) && name.startsWith(`harness-${id}-`))) fail(`Invalid capability skills: ${id}`);
59
+ || !meta.skills.every(name => validName(name) && name.startsWith(`harness-${owner}-`))) fail(`Invalid capability skills: ${id}`);
38
60
  if (!Array.isArray(meta.workflows) || !meta.workflows.length || !unique(meta.workflows)
39
61
  || !meta.workflows.every(p => /^workflows\/[a-z][a-z0-9-]*\.md$/.test(p))) fail(`Invalid capability workflows: ${id}`);
40
62
  for (const name of meta.skills) {
41
- skillMetadata(fs.readFileSync(safe(source, prefix + `skills/${name}/SKILL.md`), 'utf8'), name);
63
+ skillMetadata(fs.readFileSync(safe(source, assetPrefix + `skills/${name}/SKILL.md`), 'utf8'), name);
42
64
  }
43
65
  for (const workflow of meta.workflows) {
44
- if (!fs.readFileSync(safe(source, prefix + workflow), 'utf8').trim()) fail(`Empty capability workflow: ${id}/${workflow}`);
66
+ if (!fs.readFileSync(safe(source, assetPrefix + workflow), 'utf8').trim()) fail(`Empty capability workflow: ${id}/${workflow}`);
45
67
  }
46
68
  for (const [kind, pattern] of [
47
69
  ['agents', new RegExp('^agents/[a-z][a-z0-9-]*[.]md$')],
@@ -50,7 +72,7 @@ export function loadCapability(source, id) {
50
72
  const entries = meta[kind] ?? [];
51
73
  if (!Array.isArray(entries) || !unique(entries) || !entries.every(p => typeof p === 'string' && pattern.test(p))) fail(`Invalid capability ${kind}: ${id}`);
52
74
  for (const file of entries) {
53
- const body = fs.readFileSync(safe(source, prefix + file), 'utf8');
75
+ const body = fs.readFileSync(safe(source, assetPrefix + file), 'utf8');
54
76
  if (!body.trim()) fail(`Empty capability ${kind}: ${id}/${file}`);
55
77
  if (kind === 'agents' && !['## Inputs', '## Outputs', '## Permissions and limits', '## Handoff'].every(h => body.includes(h))) {
56
78
  fail(`Invalid capability agent contract: ${id}/${file}`);
@@ -65,10 +87,16 @@ export function loadCapability(source, id) {
65
87
  return { ...meta, agents: meta.agents ?? [], templates: meta.templates ?? [], checks, cases };
66
88
  }
67
89
 
90
+ export const RETIRED_SKILLS = {
91
+ 'harness-java-spring-kafka-outbox': 'Ask SA to assess requirements and existing contracts; no messaging pattern is selected by Harness.',
92
+ 'harness-java-spring-database-migration': 'Use the approved SA data/deployment decision and project tooling; no universal migration recipe is installed.',
93
+ 'harness-astro-cloudflare': 'Use approved hosting and provider documentation; Astro no longer selects a cloud provider.',
94
+ 'harness-astro-microfrontend': 'Use approved SA module/deployment boundaries; no microfrontend recipe is installed.',
95
+ };
68
96
  export function listCapabilities(source) {
69
97
  return (manifest(source).capabilities ?? []).map(id => {
70
- const { description, skills, workflows, agents, templates, module_version, verification, evaluation } = loadCapability(source, id);
71
- return { id, description, skills, workflows, agents, templates, module_version: module_version ?? null, verification: verification ?? null, evaluation: evaluation ?? null };
98
+ const { description, skills, workflows, agents, templates, module_version, verification, evaluation, asset_owner } = loadCapability(source, id);
99
+ return { id, description, skills, workflows, agents, templates, asset_owner: asset_owner ?? id, module_version: module_version ?? null, verification: verification ?? null, evaluation: evaluation ?? null };
72
100
  });
73
101
  }
74
102
 
@@ -86,7 +114,10 @@ export function skillCatalog(root, lock) {
86
114
  export function readSkill(root, lock, name) {
87
115
  if (!validName(name) || !name.startsWith('harness-')) fail('Invalid skill name');
88
116
  const relative = `.agents/skills/${name}/SKILL.md`;
89
- if (!Object.hasOwn(lock.files, relative)) fail(`Skill is not installed: ${name}`);
117
+ if (!Object.hasOwn(lock.files, relative)) {
118
+ if (RETIRED_SKILLS[name]) return { status: 'planned', name, retired: true, executed: false, message: RETIRED_SKILLS[name] };
119
+ fail(`Skill is not installed: ${name}`);
120
+ }
90
121
  const content = payload(root, relative).toString('utf8');
91
122
  skillMetadata(content, name);
92
123
  return { status: 'ok', name, path: relative, content };
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs';
2
+ import { safe, stat, digest, isObject, HarnessError } from './files.js';
3
+ import { readConfig } from './project-config.js';
4
+ import { redacted } from './verification-report.js';
5
+ const requireThat=(v,m)=>{if(!v)throw new HarnessError('Invalid checkpoint: '+m);};
6
+ const text=v=>typeof v==='string'&&v.trim().length>0&&v.length<=1024&&!/[\0\r\n]/.test(v);
7
+ export function checkpointPath(id) {
8
+ requireThat(typeof id==='string'&&/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(id)&&id!=='archive','change ID');
9
+ return 'openspec/changes/'+id+'/checkpoint.json';
10
+ }
11
+ function references(root,rows,verify=false) {
12
+ requireThat(Array.isArray(rows),'reference list');
13
+ return rows.map(r=>{
14
+ requireThat(isObject(r)&&text(r.path)&&/^[a-f0-9]{64}$/.test(r.sha256),'local reference/digest');
15
+ const p=safe(root,r.path),info=stat(p);
16
+ requireThat(info?.isFile()&&info.size<=1024*1024,'reference missing/oversized');
17
+ const actual=digest(fs.readFileSync(p));if(verify)requireThat(actual===r.sha256,'evidence digest mismatch');
18
+ return {...r,current_sha256:actual,current:actual===r.sha256};
19
+ });
20
+ }
21
+ export function validateCheckpoint(root,id,row) {
22
+ requireThat(isObject(row)&&row.schema_version===1&&row.change_id===id,'schema/change mismatch');redacted(row);
23
+ requireThat(text(row.phase)&&text(row.summary)&&text(row.next_action),'phase/summary/next action');
24
+ requireThat(typeof row.timestamp==='string'&&/T.*(?:Z|[+-]\d\d:\d\d)$/.test(row.timestamp)&&Number.isFinite(Date.parse(row.timestamp)),'timezone timestamp');
25
+ references(root,row.baseline);
26
+ requireThat(Array.isArray(row.completed)&&Array.isArray(row.pending)&&Array.isArray(row.blockers),'task lists');
27
+ const ids=new Set();
28
+ for(const task of [...row.completed,...row.pending,...(row.in_progress?[row.in_progress]:[])]){
29
+ requireThat(isObject(task)&&text(task.id)&&!ids.has(task.id)&&text(task.summary),'unique task ID/summary');ids.add(task.id);
30
+ }
31
+ for(const task of row.completed)requireThat(references(root,task.evidence,true).length>0,'completed task needs evidence');
32
+ requireThat(row.in_progress===null||isObject(row.in_progress),'in_progress');
33
+ requireThat(row.blockers.every(text),'blockers');
34
+ requireThat(Array.isArray(row.external_sources)&&row.external_sources.every(s=>isObject(s)&&text(s.location)&&text(s.revision)),'external source declarations');
35
+ requireThat(Array.isArray(row.operations),'operation list');const operations=new Set();
36
+ for(const op of row.operations){
37
+ requireThat(isObject(op)&&text(op.id)&&!operations.has(op.id)&&text(op.action)&&text(op.target)&&['prepared','completed','unknown'].includes(op.status),'operation receipt');operations.add(op.id);
38
+ const refs=references(root,op.evidence,true);if(op.status==='completed')requireThat(refs.length>0,'completed operation needs evidence');
39
+ }
40
+ if(row.usage!==undefined){
41
+ requireThat(isObject(row.usage)&&text(row.usage.source),'measured usage source');
42
+ for(const k of ['tokens','cost','remaining_tokens'])if(row.usage[k]!==null&&row.usage[k]!==undefined)requireThat(Number.isFinite(row.usage[k])&&row.usage[k]>=0,'usage');
43
+ if(row.usage.cost!==null&&row.usage.cost!==undefined)requireThat(text(row.usage.currency),'cost currency');
44
+ }
45
+ return row;
46
+ }
47
+ export function resumePlan(root,id) {
48
+ const name=checkpointPath(id),row=readConfig(root,name);requireThat(row!==null,'checkpoint missing');validateCheckpoint(root,id,row);
49
+ const baseline=references(root,row.baseline),pendingOperations=row.operations.filter(o=>o.status!=='completed');
50
+ const revalidate=baseline.some(r=>!r.current);
51
+ return {status:row.blockers.length?'blocked':'planned',executed:false,change_id:id,checkpoint:name,
52
+ checkpoint_sha256:digest(fs.readFileSync(safe(root,name))),baseline,revalidate_required:revalidate,
53
+ phase:row.phase,summary:row.summary,completed:row.completed,pending:row.pending,in_progress:row.in_progress,blockers:row.blockers,
54
+ next_action:row.next_action,operations_to_reconcile:pendingOperations,
55
+ external_sources:row.external_sources.map(s=>({...s,availability:'unverified'})),usage:row.usage??null,
56
+ note:'Read-only recovery plan. Reconcile changed sources, completed evidence and unknown/prepared side effects against actual host/remote state before retrying. Receipts and usage are producer-declared; no remote operation, model or quota probe.'};
57
+ }
package/cli/claude.js ADDED
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import { safe, stat, HarnessError } from './files.js';
3
+ export const CLAUDE_START = '<!-- ai-harness:claude:start -->';
4
+ export const CLAUDE_END = '<!-- ai-harness:claude:end -->';
5
+ export const CLAUDE_IMPORT = Buffer.from(CLAUDE_START + '\n@AGENTS.md\n' + CLAUDE_END);
6
+ export function claudeBlock(data) {
7
+ const a=data.indexOf(CLAUDE_START), b=data.indexOf(CLAUDE_END);
8
+ if(a===-1 && b===-1)return null;
9
+ if(a===-1 || b<a || data.indexOf(CLAUDE_START,a+CLAUDE_START.length)!==-1 || data.indexOf(CLAUDE_END,b+CLAUDE_END.length)!==-1)
10
+ throw new HarnessError('CLAUDE.md has invalid Harness markers');
11
+ return data.subarray(a,b+CLAUDE_END.length);
12
+ }
13
+ function activeText(text) {
14
+ return text.replace(/^[ \t]*(\x60{3,}|~{3,})[^\r\n]*\r?\n[\s\S]*?^[ \t]*\1[ \t]*(?:\r?\n|$)/gm,'')
15
+ .replace(/\x60[^\x60\r\n]*\x60/g,'').replace(/<!--[\s\S]*?-->/g,'');
16
+ }
17
+ export function hasAgentsImport(text, nested=false) {
18
+ return new RegExp('(?:^|\\s)@' + (nested ? '\\.\\./' : '(?:\\./)?') + 'AGENTS\\.md(?=\\s|$)').test(activeText(text));
19
+ }
20
+ export function linkClaude(existing) {
21
+ const block=claudeBlock(existing);
22
+ if(block){if(block.toString().replaceAll('\r\n','\n')!==CLAUDE_IMPORT.toString())throw new HarnessError('CLAUDE managed import differs; reconcile before linking');return existing;}
23
+ if(hasAgentsImport(existing.toString('utf8')))return existing;
24
+ const newline=existing.includes(Buffer.from('\r\n'))?'\r\n':'\n';
25
+ const separator=existing.length?(existing.at(-1)===10?newline:newline+newline):'';
26
+ return Buffer.concat([existing,Buffer.from(separator),Buffer.from(CLAUDE_IMPORT.toString().replaceAll('\n',newline)+newline)]);
27
+ }
28
+ export function claudeReport(root) {
29
+ const files=[];
30
+ for(const name of ['CLAUDE.md','CLAUDE.local.md','.claude/CLAUDE.md']){
31
+ const p=safe(root,name),info=stat(p);if(!info)continue;
32
+ if(info.size>1024*1024)throw new HarnessError('Claude instruction file exceeds 1 MiB: '+name);
33
+ const body=fs.readFileSync(p,'utf8');
34
+ files.push({path:name,imports_agents:hasAgentsImport(body,name.startsWith('.claude/'))});
35
+ }
36
+ const linked=files.some(f=>f.imports_agents);
37
+ return {status:files.length&&!linked?'warning':'unverified',files,
38
+ warnings:files.length&&!linked?['Claude instructions may shadow AGENTS.md. Review actual version/settings; opt in to init/update --link-claude or configure the host to load both.']:[],
39
+ note:'Local import inspection only. Ancestor/global files, host version/plugin/settings and actual instruction loading are unverified; confirm with Claude /context. No native settings are changed.'};
40
+ }
@@ -0,0 +1,46 @@
1
+ import { isObject, HarnessError } from './files.js';
2
+ import { redacted } from './verification-report.js';
3
+ const tasks=['spring-api','transaction-defect','maven-onboarding'];
4
+ const metrics=['human_correction_rounds','unnecessary_questions','accepted_criteria','total_criteria','elapsed_seconds','tokens','cost'];
5
+ const requireThat=(v,m)=>{if(!v)throw new HarnessError('Invalid real-task comparison: '+m);};
6
+ export function comparisonTemplate(){
7
+ return {schema_version:1,status:'unperformed',metadata:{model:null,runtime:null,harness_commit:null,starting_commit:null,budget:null},tasks,
8
+ runs:tasks.flatMap(task=>['without-harness','with-harness'].flatMap(condition=>[1,2,3].map(repeat=>({task_id:task,condition,repeat,status:'UNPERFORMED',evidence:[],usage_source:null,currency:null,...Object.fromEntries(metrics.map(m=>[m,null]))}))))};
9
+ }
10
+ function distribution(values) {
11
+ if(!values.length)return {observed_runs:0,median:null,min:null,max:null};
12
+ const sorted=[...values].sort((a,b)=>a-b),i=Math.floor(sorted.length/2);
13
+ return {observed_runs:values.length,median:sorted.length%2?sorted[i]:(sorted[i-1]+sorted[i])/2,min:sorted[0],max:sorted.at(-1)};
14
+ }
15
+ export function summarizeComparison(report){
16
+ requireThat(isObject(report)&&report.schema_version===1&&isObject(report.metadata),'schema');redacted(report);
17
+ requireThat(Array.isArray(report.tasks)&&report.tasks.length===3&&new Set(report.tasks).size===3&&report.tasks.every(t=>typeof t==='string'&&/^[a-z][a-z0-9-]*$/.test(t)),'three unique task IDs');
18
+ requireThat(Array.isArray(report.runs)&&report.runs.length===18,'18 independent run slots');
19
+ const ids=new Set();
20
+ for(const row of report.runs){
21
+ requireThat(isObject(row)&&report.tasks.includes(row.task_id)&&['with-harness','without-harness'].includes(row.condition)&&[1,2,3].includes(row.repeat),'task/condition/repeat');
22
+ const id=row.task_id+'/'+row.condition+'/'+row.repeat;requireThat(!ids.has(id),'duplicate run');ids.add(id);
23
+ requireThat(['UNPERFORMED','COMPLETED','FAILED','INTERRUPTED','BLOCKED'].includes(row.status),'status');
24
+ requireThat(Array.isArray(row.evidence)&&row.evidence.every(v=>typeof v==='string'&&v.trim()&&!/[\0\r\n]/.test(v)),'evidence');
25
+ if(row.status!=='UNPERFORMED')requireThat(row.evidence.length>0,'observed run needs evidence');
26
+ for(const m of metrics){requireThat(row[m]===null||(typeof row[m]==='number'&&Number.isFinite(row[m])&&row[m]>=0),'metric '+m);
27
+ if(['human_correction_rounds','unnecessary_questions','accepted_criteria','total_criteria','tokens'].includes(m)&&row[m]!==null)requireThat(Number.isSafeInteger(row[m]),'integer metric '+m);
28
+ }
29
+ if(row.status==='UNPERFORMED')requireThat(metrics.every(m=>row[m]===null),'unperformed metrics must stay null');
30
+ if(row.accepted_criteria!==null)requireThat(row.total_criteria!==null&&row.accepted_criteria<=row.total_criteria,'accepted/total criteria');
31
+ if(row.tokens!==null||row.cost!==null)requireThat(typeof row.usage_source==='string'&&row.usage_source.trim(),'actual usage source');
32
+ if(row.cost!==null)requireThat(typeof row.currency==='string'&&row.currency.trim(),'cost currency');
33
+ }
34
+ const currencies=new Set(report.runs.filter(r=>r.cost!==null).map(r=>r.currency));
35
+ requireThat(currencies.size<=1,'mixed cost currencies');
36
+ for(const task of report.tasks)requireThat(new Set(report.runs.filter(r=>r.task_id===task&&r.total_criteria!==null).map(r=>r.total_criteria)).size<=1,'inconsistent acceptance totals');
37
+ const observed=report.runs.some(r=>r.status!=='UNPERFORMED');
38
+ if(observed)for(const key of ['model','runtime','harness_commit','starting_commit'])requireThat(typeof report.metadata[key]==='string'&&report.metadata[key].trim(),'metadata '+key);
39
+ const summaries=report.tasks.flatMap(task=>['without-harness','with-harness'].map(condition=>{
40
+ const rows=report.runs.filter(r=>r.task_id===task&&r.condition===condition);
41
+ return {task_id:task,condition,statuses:Object.fromEntries(['UNPERFORMED','COMPLETED','FAILED','INTERRUPTED','BLOCKED'].map(s=>[s,rows.filter(r=>r.status===s).length])),
42
+ metrics:Object.fromEntries(metrics.map(m=>[m,distribution(rows.map(r=>r[m]).filter(v=>v!==null))]))};
43
+ }));
44
+ return {status:'ok',executed:false,observed_runs:report.runs.filter(r=>r.status!=='UNPERFORMED').length,summaries,
45
+ note:'Summarizes manually supplied observations only. Null is unknown, not zero. Retain all failures/interruptions; independently review evidence, equal acceptance criteria and matching model/task/budget. No productivity claim or model execution is inferred.'};
46
+ }
@@ -73,6 +73,7 @@ export function validateRegistry(source) {
73
73
  const graph = new Map();
74
74
  for (const {kind,id,data} of entries) {
75
75
  const owner=moduleRef(kind,id);
76
+ if (data.asset_owner && !declared.has(moduleRef('capability',data.asset_owner))) throw new HarnessError('Unknown capability asset owner');
76
77
  for (const field of ['requires','recommends','conflicts']) {
77
78
  for (const ref of data[field] ?? []) {
78
79
  if (!declared.has(ref)) throw new HarnessError(`Unknown ${field} reference ${ref} in ${owner}`);
package/cli/files.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
+ import { claudeBlock } from './claude.js';
4
5
  import { fileURLToPath } from 'node:url';
5
6
 
6
7
  // Resolve from the installed package, never the consumer's cwd or npm's bin symlink.
@@ -100,7 +101,7 @@ export function payload(root, name) {
100
101
  const filename = safe(root, name);
101
102
  if (!stat(filename)) return null;
102
103
  const data = fs.readFileSync(filename);
103
- return name === 'AGENTS.md' ? block(data) : data;
104
+ return name === 'AGENTS.md' ? block(data) : name === 'CLAUDE.md' ? claudeBlock(data) : data;
104
105
  }
105
106
 
106
107
  export function manifest(source) {