hunter-harness 0.1.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 (184) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/bin.js +3356 -0
  4. package/package.json +34 -0
  5. package/resources/bootstrap-ir/manifest.json +19 -0
  6. package/resources/bootstrap-ir/skills/harness-apidoc.yaml +24 -0
  7. package/resources/bootstrap-ir/skills/harness-archive.yaml +24 -0
  8. package/resources/bootstrap-ir/skills/harness-codebase-map.yaml +24 -0
  9. package/resources/bootstrap-ir/skills/harness-knowledge-ingest.yaml +24 -0
  10. package/resources/bootstrap-ir/skills/harness-package.yaml +24 -0
  11. package/resources/bootstrap-ir/skills/harness-plan.yaml +24 -0
  12. package/resources/bootstrap-ir/skills/harness-review.yaml +24 -0
  13. package/resources/bootstrap-ir/skills/harness-run.yaml +24 -0
  14. package/resources/bootstrap-ir/skills/harness-skill-optimizer.yaml +28 -0
  15. package/resources/bootstrap-ir/skills/harness-submit.yaml +24 -0
  16. package/resources/bootstrap-ir/skills/harness-sync.yaml +24 -0
  17. package/resources/bootstrap-ir/skills/harness-test.yaml +24 -0
  18. package/resources/bootstrap-ir/templates/claude-code-skill.md +12 -0
  19. package/resources/harness/general/.harness-build.json +5 -0
  20. package/resources/harness/general/CONTEXT.md +65 -0
  21. package/resources/harness/general/README.md +478 -0
  22. package/resources/harness/general/agents/harness-evaluator.md +96 -0
  23. package/resources/harness/general/agents/harness-explorer.md +72 -0
  24. package/resources/harness/general/agents/harness-reviewer.md +69 -0
  25. package/resources/harness/general/harness-archive/SKILL.md +215 -0
  26. package/resources/harness/general/harness-archive/checklist.md +52 -0
  27. package/resources/harness/general/harness-archive/reference.md +103 -0
  28. package/resources/harness/general/harness-archive/scripts/gen-manifest.ps1 +31 -0
  29. package/resources/harness/general/harness-archive/templates/render-summary.mjs +171 -0
  30. package/resources/harness/general/harness-archive/templates/summary-data-template.json +128 -0
  31. package/resources/harness/general/harness-codebase-map/SKILL.md +112 -0
  32. package/resources/harness/general/harness-codebase-map/checklist.md +126 -0
  33. package/resources/harness/general/harness-codebase-map/reference.md +364 -0
  34. package/resources/harness/general/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
  35. package/resources/harness/general/harness-codebase-map/templates/CONCERNS.md +49 -0
  36. package/resources/harness/general/harness-codebase-map/templates/CONVENTIONS.md +57 -0
  37. package/resources/harness/general/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
  38. package/resources/harness/general/harness-codebase-map/templates/STACK.md +55 -0
  39. package/resources/harness/general/harness-codebase-map/templates/STRUCTURE.md +69 -0
  40. package/resources/harness/general/harness-codebase-map/templates/TESTING.md +64 -0
  41. package/resources/harness/general/harness-codebase-map/templates/map-manifest.schema.json +73 -0
  42. package/resources/harness/general/harness-codebase-map/templates/map-summary.md +64 -0
  43. package/resources/harness/general/harness-knowledge-ingest/SKILL.md +246 -0
  44. package/resources/harness/general/harness-knowledge-ingest/design.md +842 -0
  45. package/resources/harness/general/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +42 -0
  46. package/resources/harness/general/harness-knowledge-ingest/mcp-config.example.json +10 -0
  47. package/resources/harness/general/harness-knowledge-ingest/reference.md +309 -0
  48. package/resources/harness/general/harness-knowledge-ingest/scripts/harness_knowledge.py +4082 -0
  49. package/resources/harness/general/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +273 -0
  50. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-01-10-ledger-reconciliation/reports/final/summary-data.json +43 -0
  51. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-02-14-ledger-snapshot-followup/reports/final/summary-data.json +38 -0
  52. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-03-05-webhook-contract/reports/final/summary-data.json +36 -0
  53. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/project.yaml +1 -0
  54. package/resources/harness/general/harness-knowledge-ingest/tests/test_harness_knowledge.py +1792 -0
  55. package/resources/harness/general/harness-knowledge-query/SKILL.md +164 -0
  56. package/resources/harness/general/harness-plan/SKILL.md +127 -0
  57. package/resources/harness/general/harness-plan/checklist.md +243 -0
  58. package/resources/harness/general/harness-plan/protocols.md +124 -0
  59. package/resources/harness/general/harness-plan/reference.md +376 -0
  60. package/resources/harness/general/harness-review/SKILL.md +156 -0
  61. package/resources/harness/general/harness-review/checklist.md +124 -0
  62. package/resources/harness/general/harness-review/protocols.md +68 -0
  63. package/resources/harness/general/harness-review/reference.md +86 -0
  64. package/resources/harness/general/harness-run/SKILL.md +132 -0
  65. package/resources/harness/general/harness-run/checklist.md +259 -0
  66. package/resources/harness/general/harness-run/protocols.md +78 -0
  67. package/resources/harness/general/harness-run/reference.md +834 -0
  68. package/resources/harness/general/harness-submit/SKILL.md +159 -0
  69. package/resources/harness/general/harness-submit/checklist.md +407 -0
  70. package/resources/harness/general/harness-submit/reference.md +152 -0
  71. package/resources/harness/general/harness-sync/SKILL.md +82 -0
  72. package/resources/harness/general/harness-sync/reference.md +153 -0
  73. package/resources/harness/general/harness-test/SKILL.md +180 -0
  74. package/resources/harness/general/harness-test/checklist.md +319 -0
  75. package/resources/harness/general/harness-test/pitfalls.md +260 -0
  76. package/resources/harness/general/harness-test/reference.md +791 -0
  77. package/resources/harness/general/protocols/archive-report-protocol.md +175 -0
  78. package/resources/harness/general/protocols/evidence-based-reporting-protocol.md +134 -0
  79. package/resources/harness/general/protocols/ledger-protocol.md +270 -0
  80. package/resources/harness/general/protocols/powershell-protocol.md +181 -0
  81. package/resources/harness/general/protocols/report-pipeline-protocol.md +150 -0
  82. package/resources/harness/general/protocols/sensitive-info-protocol.md +92 -0
  83. package/resources/harness/general/protocols/state-layout-protocol.md +131 -0
  84. package/resources/harness/general/protocols/submit-protocol.md +78 -0
  85. package/resources/harness/general/scripts/harness_archive.py +2319 -0
  86. package/resources/harness/general/scripts/harness_deploy.py +613 -0
  87. package/resources/harness/general/scripts/harness_events.py +680 -0
  88. package/resources/harness/general/scripts/harness_ledger.py +671 -0
  89. package/resources/harness/general/scripts/harness_preflight.py +780 -0
  90. package/resources/harness/general/scripts/harness_service.py +1331 -0
  91. package/resources/harness/java/.harness-build.json +5 -0
  92. package/resources/harness/java/CONTEXT.md +65 -0
  93. package/resources/harness/java/README.md +478 -0
  94. package/resources/harness/java/agents/harness-evaluator.md +96 -0
  95. package/resources/harness/java/agents/harness-explorer.md +72 -0
  96. package/resources/harness/java/agents/harness-reviewer.md +69 -0
  97. package/resources/harness/java/harness-apidoc/SKILL.md +86 -0
  98. package/resources/harness/java/harness-apidoc/checklist.md +142 -0
  99. package/resources/harness/java/harness-apidoc/reference.md +233 -0
  100. package/resources/harness/java/harness-archive/SKILL.md +215 -0
  101. package/resources/harness/java/harness-archive/checklist.md +52 -0
  102. package/resources/harness/java/harness-archive/reference.md +103 -0
  103. package/resources/harness/java/harness-archive/scripts/gen-manifest.ps1 +31 -0
  104. package/resources/harness/java/harness-archive/templates/render-summary.mjs +171 -0
  105. package/resources/harness/java/harness-archive/templates/summary-data-template.json +128 -0
  106. package/resources/harness/java/harness-codebase-map/SKILL.md +112 -0
  107. package/resources/harness/java/harness-codebase-map/checklist.md +126 -0
  108. package/resources/harness/java/harness-codebase-map/reference.md +364 -0
  109. package/resources/harness/java/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
  110. package/resources/harness/java/harness-codebase-map/templates/CONCERNS.md +49 -0
  111. package/resources/harness/java/harness-codebase-map/templates/CONVENTIONS.md +57 -0
  112. package/resources/harness/java/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
  113. package/resources/harness/java/harness-codebase-map/templates/STACK.md +55 -0
  114. package/resources/harness/java/harness-codebase-map/templates/STRUCTURE.md +69 -0
  115. package/resources/harness/java/harness-codebase-map/templates/TESTING.md +64 -0
  116. package/resources/harness/java/harness-codebase-map/templates/map-manifest.schema.json +73 -0
  117. package/resources/harness/java/harness-codebase-map/templates/map-summary.md +64 -0
  118. package/resources/harness/java/harness-knowledge-ingest/SKILL.md +246 -0
  119. package/resources/harness/java/harness-knowledge-ingest/design.md +842 -0
  120. package/resources/harness/java/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +42 -0
  121. package/resources/harness/java/harness-knowledge-ingest/mcp-config.example.json +10 -0
  122. package/resources/harness/java/harness-knowledge-ingest/reference.md +309 -0
  123. package/resources/harness/java/harness-knowledge-ingest/scripts/harness_knowledge.py +4082 -0
  124. package/resources/harness/java/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +273 -0
  125. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-01-10-ledger-reconciliation/reports/final/summary-data.json +43 -0
  126. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-02-14-ledger-snapshot-followup/reports/final/summary-data.json +38 -0
  127. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-03-05-webhook-contract/reports/final/summary-data.json +36 -0
  128. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/project.yaml +1 -0
  129. package/resources/harness/java/harness-knowledge-ingest/tests/test_harness_knowledge.py +1792 -0
  130. package/resources/harness/java/harness-knowledge-query/SKILL.md +164 -0
  131. package/resources/harness/java/harness-package/SKILL.md +87 -0
  132. package/resources/harness/java/harness-package/checklist.md +322 -0
  133. package/resources/harness/java/harness-package/reference.md +210 -0
  134. package/resources/harness/java/harness-plan/SKILL.md +127 -0
  135. package/resources/harness/java/harness-plan/checklist.md +243 -0
  136. package/resources/harness/java/harness-plan/protocols.md +124 -0
  137. package/resources/harness/java/harness-plan/reference.md +376 -0
  138. package/resources/harness/java/harness-review/SKILL.md +156 -0
  139. package/resources/harness/java/harness-review/checklist.md +124 -0
  140. package/resources/harness/java/harness-review/protocols.md +68 -0
  141. package/resources/harness/java/harness-review/reference.md +86 -0
  142. package/resources/harness/java/harness-run/SKILL.md +148 -0
  143. package/resources/harness/java/harness-run/checklist.md +250 -0
  144. package/resources/harness/java/harness-run/protocols.md +78 -0
  145. package/resources/harness/java/harness-run/reference.md +814 -0
  146. package/resources/harness/java/harness-submit/SKILL.md +166 -0
  147. package/resources/harness/java/harness-submit/checklist.md +407 -0
  148. package/resources/harness/java/harness-submit/reference.md +152 -0
  149. package/resources/harness/java/harness-sync/SKILL.md +82 -0
  150. package/resources/harness/java/harness-sync/reference.md +153 -0
  151. package/resources/harness/java/harness-test/SKILL.md +192 -0
  152. package/resources/harness/java/harness-test/checklist.md +317 -0
  153. package/resources/harness/java/harness-test/pitfalls-java.md +16 -0
  154. package/resources/harness/java/harness-test/pitfalls.md +260 -0
  155. package/resources/harness/java/harness-test/reference.md +788 -0
  156. package/resources/harness/java/protocols/archive-report-protocol.md +175 -0
  157. package/resources/harness/java/protocols/evidence-based-reporting-protocol.md +134 -0
  158. package/resources/harness/java/protocols/ledger-protocol.md +270 -0
  159. package/resources/harness/java/protocols/powershell-protocol.md +181 -0
  160. package/resources/harness/java/protocols/report-pipeline-protocol.md +150 -0
  161. package/resources/harness/java/protocols/sensitive-info-protocol.md +92 -0
  162. package/resources/harness/java/protocols/state-layout-protocol.md +131 -0
  163. package/resources/harness/java/protocols/submit-protocol.md +78 -0
  164. package/resources/harness/java/scripts/harness_archive.py +2319 -0
  165. package/resources/harness/java/scripts/harness_deploy.py +613 -0
  166. package/resources/harness/java/scripts/harness_events.py +680 -0
  167. package/resources/harness/java/scripts/harness_ledger.py +671 -0
  168. package/resources/harness/java/scripts/harness_preflight.py +780 -0
  169. package/resources/harness/java/scripts/harness_service.py +1331 -0
  170. package/resources/harness/manifests/general.json +296 -0
  171. package/resources/harness/manifests/java.json +324 -0
  172. package/resources/manifest.json +19 -0
  173. package/resources/skills/harness-apidoc/SKILL.md +50 -0
  174. package/resources/skills/harness-archive/SKILL.md +48 -0
  175. package/resources/skills/harness-codebase-map/SKILL.md +53 -0
  176. package/resources/skills/harness-knowledge-ingest/SKILL.md +48 -0
  177. package/resources/skills/harness-package/SKILL.md +48 -0
  178. package/resources/skills/harness-plan/SKILL.md +51 -0
  179. package/resources/skills/harness-review/SKILL.md +50 -0
  180. package/resources/skills/harness-run/SKILL.md +48 -0
  181. package/resources/skills/harness-skill-optimizer/SKILL.md +54 -0
  182. package/resources/skills/harness-submit/SKILL.md +47 -0
  183. package/resources/skills/harness-sync/SKILL.md +48 -0
  184. package/resources/skills/harness-test/SKILL.md +50 -0
package/dist/bin.js ADDED
@@ -0,0 +1,3356 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin.ts
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+ import { createInterface } from "node:readline/promises";
6
+ import { Command, CommanderError } from "commander";
7
+
8
+ // ../contracts/dist/ai-config.js
9
+ import { z as z4 } from "zod";
10
+
11
+ // ../contracts/dist/registry.js
12
+ import { z as z3 } from "zod";
13
+
14
+ // ../contracts/dist/protocol.js
15
+ import { z as z2 } from "zod";
16
+
17
+ // ../contracts/dist/file-policy.js
18
+ import { z } from "zod";
19
+ var fileKindSchema = z.enum([
20
+ "user_editable",
21
+ "generated_reviewable",
22
+ "internal_state",
23
+ "generated_cache",
24
+ "external_unmanaged"
25
+ ]);
26
+ var editPolicySchema = z.enum([
27
+ "allow",
28
+ "managed-block-only",
29
+ "discourage",
30
+ "protocol-only",
31
+ "external"
32
+ ]);
33
+ var pushPolicySchema = z.enum([
34
+ "diff-proposal",
35
+ "full-diff-proposal",
36
+ "confirm-before-proposal",
37
+ "never"
38
+ ]);
39
+ var updatePolicySchema = z.enum([
40
+ "managed-block-only",
41
+ "skip-if-local-dirty",
42
+ "replace-if-baseline-clean",
43
+ "protocol-only",
44
+ "protocol-rebuild-only",
45
+ "never"
46
+ ]);
47
+ var conflictPolicySchema = z.enum([
48
+ "skip-and-report",
49
+ "managed-block-skip",
50
+ "transactional-replace",
51
+ "protocol-recover",
52
+ "ignore"
53
+ ]);
54
+ var filePolicySchema = z.object({
55
+ file_kind: fileKindSchema,
56
+ edit_policy: editPolicySchema,
57
+ push_policy: pushPolicySchema,
58
+ update_policy: updatePolicySchema,
59
+ conflict_policy: conflictPolicySchema
60
+ }).strict();
61
+
62
+ // ../contracts/dist/protocol.js
63
+ var sha256Schema = z2.string().regex(/^sha256:[a-f0-9]{64}$/);
64
+ var relativePathSchema = z2.string().min(1).refine((path) => !path.startsWith("/") && !path.startsWith("\\") && !/^[A-Za-z]:/.test(path), "path must be relative");
65
+ var commonFileFields = {
66
+ file_kind: fileKindSchema
67
+ };
68
+ var addOperationSchema = z2.object({
69
+ ...commonFileFields,
70
+ operation: z2.literal("add"),
71
+ path: relativePathSchema,
72
+ content_sha256: sha256Schema,
73
+ size_bytes: z2.number().int().nonnegative(),
74
+ block_id: z2.string().optional()
75
+ // Y-1 per-id managed block(COM-005 决策 B:前向兼容,当前 CLI 已接受;旧 CLI 反向拒绝分支不可达,不实现 MANIFEST_PROTOCOL_UNSUPPORTED)
76
+ }).strict();
77
+ var modifyOperationSchema = z2.object({
78
+ ...commonFileFields,
79
+ operation: z2.literal("modify"),
80
+ path: relativePathSchema,
81
+ base_content_sha256: sha256Schema,
82
+ content_sha256: sha256Schema,
83
+ size_bytes: z2.number().int().nonnegative(),
84
+ block_id: z2.string().optional()
85
+ // Y-1 per-id managed block(COM-005 决策 B:前向兼容,当前 CLI 已接受;旧 CLI 反向拒绝分支不可达,不实现 MANIFEST_PROTOCOL_UNSUPPORTED)
86
+ }).strict();
87
+ var deleteOperationSchema = z2.object({
88
+ ...commonFileFields,
89
+ operation: z2.literal("delete"),
90
+ path: relativePathSchema,
91
+ base_content_sha256: sha256Schema,
92
+ tombstone: z2.object({
93
+ deleted_at: z2.iso.datetime(),
94
+ reason: z2.string().min(1),
95
+ previous_sha256: sha256Schema
96
+ }).strict()
97
+ }).strict();
98
+ var renameOperationSchema = z2.object({
99
+ ...commonFileFields,
100
+ operation: z2.literal("rename"),
101
+ from_path: relativePathSchema,
102
+ to_path: relativePathSchema,
103
+ base_content_sha256: sha256Schema,
104
+ content_sha256: sha256Schema,
105
+ size_bytes: z2.number().int().nonnegative()
106
+ }).strict();
107
+ var fileOperationSchema = z2.discriminatedUnion("operation", [
108
+ addOperationSchema,
109
+ modifyOperationSchema,
110
+ deleteOperationSchema,
111
+ renameOperationSchema
112
+ ]);
113
+ var artifactManifestSchema = z2.object({
114
+ schema_version: z2.literal(1),
115
+ project_id: z2.string().regex(/^prj_/),
116
+ project_version: z2.string().regex(/^pv_/).nullable(),
117
+ artifact_id: z2.string().regex(/^art_/),
118
+ files: z2.array(fileOperationSchema),
119
+ manifest_sha256: sha256Schema
120
+ }).strict();
121
+ var baselineFileSchema = z2.object({
122
+ baseline_hash: sha256Schema.nullable(),
123
+ local_hash_at_apply: sha256Schema.nullable(),
124
+ file_kind: fileKindSchema,
125
+ adapter: z2.string().optional(),
126
+ canonical_target: z2.string().optional(),
127
+ managed_block_hash: sha256Schema.optional(),
128
+ last_applied_version: z2.string().nullable(),
129
+ deleted: z2.boolean()
130
+ }).strict();
131
+ var baselineManifestSchema = z2.object({
132
+ schema_version: z2.literal(1),
133
+ project_id: z2.string().regex(/^prj_/).nullable(),
134
+ complete_project_version: z2.string().regex(/^pv_/).nullable(),
135
+ artifact_manifest_hash: sha256Schema.nullable(),
136
+ files: z2.record(z2.string(), baselineFileSchema)
137
+ }).strict();
138
+ var requestMetadataSchema = z2.object({
139
+ request_id: z2.uuid(),
140
+ idempotency_key: z2.uuid(),
141
+ project_id: z2.string().regex(/^prj_/),
142
+ client_id: z2.string().regex(/^cli_/),
143
+ base_project_version: z2.string().regex(/^pv_/).nullable(),
144
+ base_manifest_hash: sha256Schema,
145
+ protocol_version: z2.literal(1)
146
+ }).strict();
147
+
148
+ // ../contracts/dist/registry.js
149
+ var registryAgentSchema = z3.enum(["claude-code", "codex", "cursor", "generic", "mcp"]);
150
+ var registrySkillStatusSchema = z3.enum([
151
+ "draft",
152
+ "pending_review",
153
+ "published",
154
+ "rejected",
155
+ "deprecated"
156
+ ]);
157
+ var registrySkillProposalStatusSchema = z3.enum([
158
+ "pending_review",
159
+ "approved",
160
+ "rejected"
161
+ ]);
162
+ var registrySemverSchema = z3.string().regex(/^\d+\.\d+\.\d+$/);
163
+ var registrySlugSchema = z3.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
164
+ var checkStatusSchema = z3.enum(["green", "yellow", "red"]);
165
+ var sourceFileSchema = z3.object({
166
+ path: z3.string(),
167
+ content: z3.string()
168
+ }).strict();
169
+ var SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
170
+ var skillNameSchema = z3.string().regex(SKILL_NAME_REGEX, "name must be lowercase alphanumeric with single hyphens between segments, at most 64 chars").max(64);
171
+ var skillFrontmatterSchema = z3.object({
172
+ name: skillNameSchema,
173
+ description: z3.string().min(1),
174
+ kind: z3.enum(["workflow", "tooling", "migration", "governance"]).optional(),
175
+ triggers: z3.array(z3.string()).optional(),
176
+ inputs: z3.array(z3.string()).optional(),
177
+ outputs: z3.array(z3.string()).optional(),
178
+ forbidden_actions: z3.array(z3.string()).optional(),
179
+ required_context: z3.array(z3.string()).optional(),
180
+ version: z3.string().optional()
181
+ }).passthrough();
182
+ var skillUsageExampleSchema = z3.object({
183
+ title: z3.string(),
184
+ description: z3.string(),
185
+ request: z3.string(),
186
+ result: z3.string(),
187
+ files: z3.array(z3.string()).default([])
188
+ }).strict();
189
+ var agentSkillConfigSchema = z3.object({
190
+ agent: registryAgentSchema,
191
+ enabled: z3.boolean(),
192
+ isDefault: z3.boolean(),
193
+ installTarget: z3.string(),
194
+ // per-agent 独立版本已启用:每个 agent 持独立 latestVersion/draftVersion/draft 文件包;
195
+ // publish 只前进当前 agent 的 latestVersion,不影响其他 agent(见 server store.agentsFor/publish)。
196
+ latestVersion: registrySemverSchema.nullable(),
197
+ draftVersion: registrySemverSchema.nullable(),
198
+ sourcePackagePath: z3.string().nullable()
199
+ }).strict();
200
+ var skillCheckItemSchema = z3.object({
201
+ id: z3.string(),
202
+ label: z3.string(),
203
+ status: checkStatusSchema,
204
+ message: z3.string(),
205
+ filePath: z3.string().nullable(),
206
+ fixable: z3.boolean()
207
+ }).strip();
208
+ var skillCheckResultSchema = z3.object({
209
+ items: z3.array(skillCheckItemSchema),
210
+ summary: z3.object({
211
+ green: z3.number().int(),
212
+ yellow: z3.number().int(),
213
+ red: z3.number().int()
214
+ }).strip(),
215
+ checkedAt: z3.string()
216
+ }).strip();
217
+ var draftStateSchema = z3.object({
218
+ slug: z3.string(),
219
+ agent: registryAgentSchema,
220
+ sourceFiles: z3.array(sourceFileSchema),
221
+ ir: z3.unknown().optional(),
222
+ examples: z3.array(skillUsageExampleSchema).default([]),
223
+ draftVersion: registrySemverSchema.nullable(),
224
+ checks: skillCheckResultSchema.nullable(),
225
+ aiChecks: skillCheckResultSchema.nullable().default(null),
226
+ releaseNote: z3.string().nullable(),
227
+ revision: z3.number().int(),
228
+ created_at: z3.string(),
229
+ updated_at: z3.string()
230
+ }).strict();
231
+ var publishSkillRequestSchema = z3.object({
232
+ version: registrySemverSchema,
233
+ releaseNote: z3.string().optional()
234
+ }).strict();
235
+ var setDefaultAgentRequestSchema = z3.object({
236
+ defaultAgent: registryAgentSchema,
237
+ revision: z3.number().int().positive()
238
+ }).strict();
239
+ var skillDiffFileSchema = z3.object({
240
+ path: z3.string(),
241
+ status: z3.enum(["modified", "added", "removed"]),
242
+ publishedContent: z3.string().nullable(),
243
+ draftContent: z3.string().nullable()
244
+ }).strict();
245
+ var registryValidationSchema = z3.object({
246
+ schema_valid: z3.boolean(),
247
+ sensitive_findings: z3.number().int().nonnegative(),
248
+ // Y-4:语义已扩展为"任一 installable adapter 可编译"(非仅 claude-code;store.ts createProposal/publish 验 buildArtifacts.length>0)。
249
+ // 字段名保留以维持已发布契约稳定(schemas.test.ts 引用,改名=破坏性)。
250
+ claude_compilable: z3.boolean()
251
+ }).strict();
252
+ var registryArtifactSchema = z3.object({
253
+ artifact_id: z3.string().regex(/^ska_/),
254
+ skill_slug: registrySlugSchema,
255
+ version: registrySemverSchema,
256
+ agent: registryAgentSchema,
257
+ content_sha256: sha256Schema,
258
+ size_bytes: z3.number().int().nonnegative(),
259
+ source_proposal_id: z3.string().regex(/^skp_/).nullable(),
260
+ created_at: z3.iso.datetime()
261
+ }).strict();
262
+ var registrySkillVersionSchema = z3.object({
263
+ skill_slug: registrySlugSchema,
264
+ version: registrySemverSchema,
265
+ agent: registryAgentSchema,
266
+ ir: z3.unknown().optional(),
267
+ artifacts: z3.array(registryArtifactSchema),
268
+ source_proposal_id: z3.string().regex(/^skp_/).nullable(),
269
+ sourceFiles: z3.array(sourceFileSchema).default([]),
270
+ examples: z3.array(skillUsageExampleSchema).default([]),
271
+ changeNote: z3.string().nullable(),
272
+ created_at: z3.iso.datetime()
273
+ }).strict();
274
+ var registrySkillSummarySchema = z3.object({
275
+ skill_id: z3.string().regex(/^skl_/),
276
+ slug: registrySlugSchema,
277
+ name: z3.string().min(1),
278
+ description: z3.string().min(1),
279
+ // kind 从 entry frontmatter 反范式化(与 description 同理),供 dashboard 分类分布。
280
+ // nullable:新 skill 无 kind 时为 null(overview 回退 "unknown");optional:兼容旧数据无此字段。
281
+ kind: z3.enum(["workflow", "tooling", "migration", "governance"]).nullable().optional(),
282
+ tags: z3.array(registrySlugSchema),
283
+ status: registrySkillStatusSchema,
284
+ latest_version: registrySemverSchema.nullable(),
285
+ defaultAgent: registryAgentSchema.nullable(),
286
+ agents: z3.array(agentSkillConfigSchema),
287
+ revision: z3.number().int().positive(),
288
+ created_at: z3.iso.datetime(),
289
+ updated_at: z3.iso.datetime()
290
+ }).strict();
291
+ var registrySkillDetailSchema = registrySkillSummarySchema.extend({
292
+ ir: z3.unknown().optional(),
293
+ sourceFiles: z3.array(sourceFileSchema).default([]),
294
+ examples: z3.array(skillUsageExampleSchema).default([])
295
+ }).strict();
296
+ var registrySkillProposalSchema = z3.object({
297
+ proposal_id: z3.string().regex(/^skp_/),
298
+ skill_slug: registrySlugSchema,
299
+ proposed_ir: z3.unknown().optional(),
300
+ status: registrySkillProposalStatusSchema,
301
+ created_by: z3.string().min(1),
302
+ validation: registryValidationSchema,
303
+ created_at: z3.iso.datetime(),
304
+ reviewed_at: z3.iso.datetime().nullable()
305
+ }).strict();
306
+ var registryTagSchema = z3.object({
307
+ tag_id: z3.string().regex(/^tag_/),
308
+ slug: registrySlugSchema,
309
+ label: z3.string().min(1).max(80),
310
+ active: z3.boolean(),
311
+ revision: z3.number().int().positive(),
312
+ usageCount: z3.number().int().nonnegative(),
313
+ created_at: z3.iso.datetime(),
314
+ updated_at: z3.iso.datetime()
315
+ }).strict();
316
+ var registryWorkflowSchema = z3.object({
317
+ workflow_id: z3.string().regex(/^wf_/),
318
+ key: registrySlugSchema,
319
+ name: z3.string().min(1).max(120),
320
+ description: z3.string().min(1).max(1e3),
321
+ profile: registrySlugSchema,
322
+ default_agent: registryAgentSchema,
323
+ enabled: z3.boolean(),
324
+ skill_slugs: z3.array(registrySlugSchema),
325
+ revision: z3.number().int().positive(),
326
+ created_at: z3.iso.datetime(),
327
+ updated_at: z3.iso.datetime()
328
+ }).strict();
329
+ var registryWorkflowMutationSchema = z3.object({
330
+ key: registrySlugSchema,
331
+ name: z3.string().min(1).max(120),
332
+ description: z3.string().min(1).max(1e3),
333
+ profile: registrySlugSchema,
334
+ default_agent: registryAgentSchema,
335
+ enabled: z3.boolean(),
336
+ skill_slugs: z3.array(registrySlugSchema)
337
+ }).strict();
338
+ var registryProjectWorkflowBindingSchema = z3.object({
339
+ project_id: z3.string().regex(/^prj_/),
340
+ workflow_id: z3.string().regex(/^wf_/),
341
+ revision: z3.number().int().positive(),
342
+ updated_at: z3.iso.datetime()
343
+ }).strict();
344
+
345
+ // ../contracts/dist/ai-config.js
346
+ var aiProviderApiFormatSchema = z4.enum(["openai", "anthropic", "custom"]);
347
+ var providerModelSchema = z4.object({
348
+ id: z4.string(),
349
+ display_model: z4.string(),
350
+ request_model: z4.string(),
351
+ input_cost: z4.number().nonnegative().default(0),
352
+ output_cost: z4.number().nonnegative().default(0),
353
+ cache_hit_cost: z4.number().nonnegative().default(0),
354
+ cache_create_cost: z4.number().nonnegative().default(0)
355
+ }).strict();
356
+ var aiProviderConfigSchema = z4.object({
357
+ provider_id: z4.string(),
358
+ label: z4.string(),
359
+ base_url: z4.url(),
360
+ model: z4.string(),
361
+ enabled: z4.boolean(),
362
+ is_default: z4.boolean(),
363
+ api_key_env: z4.string(),
364
+ revision: z4.number().int(),
365
+ daily_request_limit: z4.number().int().nonnegative().nullable().default(null),
366
+ daily_token_limit: z4.number().int().nonnegative().nullable().default(null),
367
+ created_at: z4.iso.datetime(),
368
+ updated_at: z4.iso.datetime(),
369
+ models: z4.array(providerModelSchema).default([]),
370
+ api_format: aiProviderApiFormatSchema.default("openai"),
371
+ note: z4.string().default(""),
372
+ website: z4.string().default(""),
373
+ selected_model_id: z4.string().nullable().default(null),
374
+ sort_order: z4.number().int().nonnegative().default(0)
375
+ }).strict();
376
+ var aiProviderWithKeySetSchema = aiProviderConfigSchema.extend({
377
+ key_set: z4.boolean()
378
+ }).strict();
379
+ var aiQuotaUsageSchema = z4.object({
380
+ provider_id: z4.string(),
381
+ date: z4.string(),
382
+ requests: z4.number().int().nonnegative(),
383
+ tokens: z4.number().int().nonnegative(),
384
+ model: z4.string().default(""),
385
+ input_tokens: z4.number().int().nonnegative().default(0),
386
+ output_tokens: z4.number().int().nonnegative().default(0),
387
+ cache_hit_tokens: z4.number().int().nonnegative().default(0),
388
+ cache_create_tokens: z4.number().int().nonnegative().default(0),
389
+ cost: z4.number().nonnegative().default(0)
390
+ }).strict();
391
+ var aiConfigStateSchema = z4.object({
392
+ defaultProvider: z4.string().nullable(),
393
+ providers: z4.array(aiProviderConfigSchema),
394
+ usage: z4.array(aiQuotaUsageSchema).default([])
395
+ }).strict();
396
+ var aiProviderReorderRequestSchema = z4.object({
397
+ schema_version: z4.literal(1),
398
+ provider_ids: z4.array(z4.string().min(1)).min(1)
399
+ }).strict();
400
+ var aiJobStateSchema = z4.object({
401
+ jobId: z4.string(),
402
+ slug: z4.string(),
403
+ agent: registryAgentSchema,
404
+ status: z4.enum(["pending", "running", "completed", "failed"]),
405
+ result: skillCheckResultSchema.nullable(),
406
+ error: z4.string().nullable(),
407
+ createdAt: z4.iso.datetime(),
408
+ expiresAt: z4.iso.datetime()
409
+ }).strict();
410
+
411
+ // ../contracts/dist/canonical-json.js
412
+ function normalize(value) {
413
+ if (Array.isArray(value)) {
414
+ return value.map(normalize);
415
+ }
416
+ if (value !== null && typeof value === "object") {
417
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, normalize(item)]));
418
+ }
419
+ return value;
420
+ }
421
+ function canonicalJson(value) {
422
+ return JSON.stringify(normalize(value));
423
+ }
424
+
425
+ // ../contracts/dist/errors.js
426
+ import { z as z5 } from "zod";
427
+ var apiErrorCodeSchema = z5.enum([
428
+ "VALIDATION_FAILED",
429
+ "INVALID_CURSOR",
430
+ "INVALID_PATH",
431
+ "AUTH_REQUIRED",
432
+ "TOKEN_INVALID",
433
+ "AUTH_FORBIDDEN",
434
+ "PROJECT_BIND_FORBIDDEN",
435
+ "PROJECT_NOT_FOUND",
436
+ "PROPOSAL_NOT_FOUND",
437
+ "ARTIFACT_NOT_FOUND",
438
+ "IDEMPOTENCY_KEY_REUSED",
439
+ "PROJECT_BINDING_CONFLICT",
440
+ "PROJECT_VERSION_CONFLICT",
441
+ "BLOB_NOT_DECLARED",
442
+ "UPLOAD_SESSION_EXPIRED",
443
+ "FILE_TOO_LARGE",
444
+ "PROPOSAL_TOO_LARGE",
445
+ "UPLOAD_CHUNK_HASH_MISMATCH",
446
+ "ARTIFACT_HASH_MISMATCH",
447
+ "POLICY_PATH_FORBIDDEN",
448
+ "SENSITIVE_CONTENT_BLOCKED",
449
+ "RATE_LIMITED",
450
+ "INTERNAL_ERROR",
451
+ "SERVICE_UNAVAILABLE",
452
+ "CONFLICT_LOCAL_DIRTY",
453
+ "LOCAL_LOCKED",
454
+ "LOCAL_TRANSACTION_RECOVERY_REQUIRED",
455
+ "SKILL_VALIDATION_FAILED",
456
+ "SKILL_ENTRY_NOT_FOUND",
457
+ "SKILL_NOT_FOUND",
458
+ "DRAFT_NOT_FOUND",
459
+ "REVISION_CONFLICT",
460
+ "ADAPTER_NOT_INSTALLABLE",
461
+ "WORKFLOW_PACKAGE_REDIRECT"
462
+ ]);
463
+ var apiErrorEnvelopeSchema = z5.object({
464
+ error: z5.object({
465
+ code: apiErrorCodeSchema,
466
+ message: z5.string().min(1),
467
+ request_id: z5.uuid(),
468
+ details: z5.record(z5.string(), z5.unknown())
469
+ }).strict()
470
+ }).strict();
471
+ var cliExitCodeSchema = z5.union([
472
+ z5.literal(0),
473
+ z5.literal(1),
474
+ z5.literal(2),
475
+ z5.literal(3),
476
+ z5.literal(4),
477
+ z5.literal(5),
478
+ z5.literal(6),
479
+ z5.literal(7),
480
+ z5.literal(8)
481
+ ]);
482
+
483
+ // ../contracts/dist/fix.js
484
+ import { z as z6 } from "zod";
485
+ var fixActionSchema = z6.enum(["auto", "confirm", "suggest"]);
486
+ var fixPlanItemSchema = z6.object({
487
+ checkId: z6.string(),
488
+ action: fixActionSchema,
489
+ label: z6.string(),
490
+ affectedPaths: z6.array(z6.string()),
491
+ riskDelta: z6.string().nullable(),
492
+ message: z6.string(),
493
+ // AI 内容生成切片:fix-suggestions 端点填充(fixer.buildFixPatch 填的 item 不带,向后兼容)
494
+ suggestedContent: z6.string().nullable().optional(),
495
+ explanation: z6.string().nullable().optional(),
496
+ appliesTo: z6.enum(["examples", "allowed_capabilities", "instructions", "description", "tags"]).nullable().optional(),
497
+ generatedAt: z6.string().nullable().optional()
498
+ }).strict();
499
+ var fixPlanSchema = z6.object({
500
+ items: z6.array(fixPlanItemSchema),
501
+ mergedFiles: z6.array(skillDiffFileSchema),
502
+ summary: z6.object({
503
+ autoCount: z6.number().int(),
504
+ confirmCount: z6.number().int(),
505
+ suggestCount: z6.number().int(),
506
+ changedFiles: z6.number().int(),
507
+ changedLines: z6.number().int()
508
+ })
509
+ }).strict();
510
+
511
+ // ../contracts/dist/knowledge.js
512
+ import { z as z7 } from "zod";
513
+ var knowledgeStatusSchema = z7.enum([
514
+ "candidate",
515
+ "active",
516
+ "stale",
517
+ "deprecated",
518
+ "superseded"
519
+ ]);
520
+ var knowledgeFrontmatterSchema = z7.object({
521
+ id: z7.string().regex(/^knowledge\.[a-z0-9.-]+$/),
522
+ type: z7.enum([
523
+ "business",
524
+ "architecture",
525
+ "decision",
526
+ "pitfall",
527
+ "api",
528
+ "glossary",
529
+ "project-local"
530
+ ]),
531
+ scope: z7.enum([
532
+ "global",
533
+ "project",
534
+ "module",
535
+ "feature",
536
+ "api",
537
+ "business-domain",
538
+ "local"
539
+ ]),
540
+ confidence: z7.enum(["verified", "inferred", "unverified", "low"]),
541
+ status: knowledgeStatusSchema,
542
+ domains: z7.array(z7.string()),
543
+ modules: z7.array(z7.string()),
544
+ related_paths: z7.array(z7.string()),
545
+ source: z7.object({
546
+ kind: z7.enum(["manual", "archive", "review", "imported", "test", "design"]),
547
+ ref: z7.string().min(1)
548
+ }).strict(),
549
+ created_at: z7.iso.datetime(),
550
+ updated_at: z7.iso.datetime(),
551
+ last_verified_at: z7.iso.datetime().nullable(),
552
+ expires_at: z7.iso.datetime().nullable(),
553
+ supersedes: z7.array(z7.string()),
554
+ superseded_by: z7.array(z7.string())
555
+ }).strict();
556
+
557
+ // ../contracts/dist/mcp-contract.js
558
+ import { z as z8 } from "zod";
559
+ var mcpToolContractSchema = z8.object({
560
+ tool_name: z8.string(),
561
+ description: z8.string(),
562
+ input_schema: z8.record(z8.string(), z8.unknown())
563
+ }).strict();
564
+
565
+ // ../contracts/dist/project.js
566
+ import { z as z9 } from "zod";
567
+ var projectIdSchema = z9.string().regex(/^prj_[A-Za-z0-9_-]+$/);
568
+ var tokenEnvSchema = z9.string().regex(/^[A-Z_][A-Z0-9_]*$/);
569
+ var httpsUrlSchema = z9.url().refine((value) => new URL(value).protocol === "https:", "server URL must use HTTPS");
570
+ var adapterNameSchema = z9.enum([
571
+ "claude-code",
572
+ "codex",
573
+ "cursor",
574
+ "generic",
575
+ "mcp"
576
+ ]);
577
+ var initConfigSchema = z9.object({
578
+ adapter: adapterNameSchema,
579
+ profile: z9.enum(["general", "java"]),
580
+ server_url: httpsUrlSchema.nullable().optional(),
581
+ token_env: tokenEnvSchema.nullable().optional(),
582
+ project_id: projectIdSchema.nullable().optional(),
583
+ features: z9.object({
584
+ codegraph_check: z9.boolean().default(true),
585
+ superpowers_check: z9.boolean().default(true)
586
+ }).strict().optional()
587
+ }).strict();
588
+ var projectConfigSchema = z9.object({
589
+ harness: z9.object({
590
+ name: z9.literal("hunter-harness"),
591
+ schema_version: z9.literal(1)
592
+ }).strict(),
593
+ project: z9.object({
594
+ name: z9.string().min(1),
595
+ root: z9.literal("."),
596
+ local_project_key: z9.uuid(),
597
+ project_id: projectIdSchema.nullable(),
598
+ profiles: z9.array(z9.string().min(1)).min(1)
599
+ }).strict(),
600
+ server: z9.object({
601
+ url: httpsUrlSchema.nullable(),
602
+ token_env: tokenEnvSchema
603
+ }).strict(),
604
+ adapters: z9.object({
605
+ enabled: z9.array(adapterNameSchema).min(1)
606
+ }).strict()
607
+ }).strict();
608
+
609
+ // ../contracts/dist/dashboard.js
610
+ import { z as z10 } from "zod";
611
+ var dashboardHealthStatusSchema = z10.enum(["healthy", "attention", "unavailable"]);
612
+ var dashboardServiceStatusSchema = z10.enum(["operational", "degraded", "unavailable"]);
613
+ var dashboardTrendPointSchema = z10.object({
614
+ date: z10.string().regex(/^\d{4}-\d{2}-\d{2}$/),
615
+ submitted: z10.number().int().nonnegative(),
616
+ approved: z10.number().int().nonnegative(),
617
+ rejected: z10.number().int().nonnegative(),
618
+ pending: z10.number().int().nonnegative()
619
+ }).strict();
620
+ var dashboardDistributionItemSchema = z10.object({
621
+ key: z10.string().min(1),
622
+ count: z10.number().int().nonnegative()
623
+ }).strict();
624
+ var dashboardHealthItemSchema = z10.object({
625
+ key: z10.string().min(1),
626
+ label: z10.string().min(1),
627
+ status: dashboardHealthStatusSchema,
628
+ value: z10.string().min(1),
629
+ detail: z10.string().min(1)
630
+ }).strict();
631
+ var dashboardServiceItemSchema = z10.object({
632
+ key: z10.string().min(1),
633
+ label: z10.string().min(1),
634
+ status: dashboardServiceStatusSchema,
635
+ detail: z10.string().min(1),
636
+ checked_at: z10.iso.datetime()
637
+ }).strict();
638
+ var dashboardActivitySchema = z10.object({
639
+ event_id: z10.string().min(1),
640
+ action: z10.string().min(1),
641
+ target_id: z10.string().min(1),
642
+ project_id: z10.string().nullable(),
643
+ actor_id: z10.string().min(1),
644
+ created_at: z10.iso.datetime()
645
+ }).strict();
646
+ var dashboardOverviewSchema = z10.object({
647
+ generated_at: z10.iso.datetime(),
648
+ window: z10.object({
649
+ days: z10.number().int().min(7).max(30),
650
+ starts_at: z10.iso.datetime(),
651
+ ends_at: z10.iso.datetime()
652
+ }).strict(),
653
+ metrics: z10.object({
654
+ projects: z10.number().int().nonnegative(),
655
+ workflows: z10.number().int().nonnegative(),
656
+ skills: z10.number().int().nonnegative(),
657
+ published_skills: z10.number().int().nonnegative(),
658
+ pending_reviews: z10.number().int().nonnegative(),
659
+ approved_proposals: z10.number().int().nonnegative(),
660
+ rejected_proposals: z10.number().int().nonnegative(),
661
+ artifacts: z10.number().int().nonnegative(),
662
+ project_artifacts: z10.number().int().nonnegative(),
663
+ skill_artifacts: z10.number().int().nonnegative()
664
+ }).strict(),
665
+ trend: z10.array(dashboardTrendPointSchema),
666
+ distributions: z10.object({
667
+ skill_categories: z10.array(dashboardDistributionItemSchema),
668
+ workflow_profiles: z10.array(dashboardDistributionItemSchema)
669
+ }).strict(),
670
+ health: z10.array(dashboardHealthItemSchema),
671
+ services: z10.array(dashboardServiceItemSchema),
672
+ activity: z10.array(dashboardActivitySchema)
673
+ }).strict();
674
+
675
+ // ../contracts/dist/workflow-package.js
676
+ import { z as z11 } from "zod";
677
+ var workflowPackageManifestSchema = z11.object({
678
+ key: registrySlugSchema,
679
+ name: z11.string().min(1).max(120),
680
+ description: z11.string().min(1).max(1e3),
681
+ profile: registrySlugSchema,
682
+ skills: z11.array(z11.object({
683
+ slug: registrySlugSchema,
684
+ ref: z11.string()
685
+ }).strict()),
686
+ agents: z11.array(z11.object({
687
+ path: z11.string(),
688
+ ref: z11.string()
689
+ }).strict()),
690
+ protocols: z11.array(z11.object({
691
+ path: z11.string(),
692
+ ref: z11.string()
693
+ }).strict()),
694
+ templates: z11.array(z11.object({
695
+ path: z11.string(),
696
+ ref: z11.string()
697
+ }).strict()),
698
+ execution_order: z11.array(z11.string()),
699
+ strategy: z11.enum(["sequential", "parallel", "manual"])
700
+ }).strict();
701
+ var workflowPackageArtifactSchema = z11.object({
702
+ artifact_id: z11.string().regex(/^wfa_/),
703
+ package_key: registrySlugSchema,
704
+ version: registrySemverSchema,
705
+ content_sha256: sha256Schema,
706
+ size_bytes: z11.number().int().nonnegative(),
707
+ created_at: z11.iso.datetime()
708
+ }).strict();
709
+ var workflowPackageVersionSchema = z11.object({
710
+ package_key: registrySlugSchema,
711
+ version: registrySemverSchema,
712
+ manifest: workflowPackageManifestSchema,
713
+ artifacts: z11.array(workflowPackageArtifactSchema),
714
+ sourceFiles: z11.array(sourceFileSchema).default([]),
715
+ changeNote: z11.string().nullable(),
716
+ created_at: z11.iso.datetime()
717
+ }).strict();
718
+ var workflowPackageDraftStateSchema = z11.object({
719
+ key: registrySlugSchema,
720
+ manifest: workflowPackageManifestSchema,
721
+ sourceFiles: z11.array(sourceFileSchema),
722
+ draftVersion: registrySemverSchema.nullable(),
723
+ checks: skillCheckResultSchema.nullable(),
724
+ releaseNote: z11.string().nullable(),
725
+ revision: z11.number().int(),
726
+ created_at: z11.string(),
727
+ updated_at: z11.string()
728
+ }).strict();
729
+ var workflowPackageSchema = z11.object({
730
+ package_id: z11.string().regex(/^wfp_/),
731
+ key: registrySlugSchema,
732
+ manifest: workflowPackageManifestSchema,
733
+ latestVersion: registrySemverSchema.nullable(),
734
+ revision: z11.number().int().positive(),
735
+ created_at: z11.iso.datetime(),
736
+ updated_at: z11.iso.datetime()
737
+ }).strict();
738
+ var publishWorkflowPackageRequestSchema = z11.object({
739
+ version: registrySemverSchema,
740
+ releaseNote: z11.string().optional()
741
+ }).strict();
742
+
743
+ // ../core/dist/fs/hash.js
744
+ import { createHash } from "node:crypto";
745
+ import { readFile } from "node:fs/promises";
746
+ function sha256Bytes(content) {
747
+ return "sha256:" + createHash("sha256").update(content).digest("hex");
748
+ }
749
+ async function sha256File(path) {
750
+ return sha256Bytes(await readFile(path));
751
+ }
752
+
753
+ // ../core/dist/fs/path-safety.js
754
+ import { lstat } from "node:fs/promises";
755
+ import { isAbsolute, join, posix, resolve, win32 } from "node:path";
756
+ var WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
757
+ var WINDOWS_ILLEGAL = /[<>:"|?*]/;
758
+ var MAX_MANAGED_PATH = 240;
759
+ var UnsafePathError = class extends Error {
760
+ constructor(message) {
761
+ super(message);
762
+ this.name = "UnsafePathError";
763
+ }
764
+ };
765
+ function hasIllegalWindowsCharacter(segment) {
766
+ return WINDOWS_ILLEGAL.test(segment) || Array.from(segment).some((character) => character.charCodeAt(0) <= 31);
767
+ }
768
+ function normalizeManagedPath(input) {
769
+ if (input.length === 0 || input.length > MAX_MANAGED_PATH) {
770
+ throw new UnsafePathError("path is empty or exceeds the managed path limit");
771
+ }
772
+ if (input.includes("\\") || isAbsolute(input) || win32.isAbsolute(input)) {
773
+ throw new UnsafePathError("absolute paths and backslashes are not allowed");
774
+ }
775
+ const rawSegments = input.split("/");
776
+ if (rawSegments.some((segment) => segment === "..")) {
777
+ throw new UnsafePathError("path traversal is not allowed");
778
+ }
779
+ const normalized = posix.normalize(input).replace(/^\.\/+/, "").replace(/\/+/g, "/");
780
+ if (normalized === "." || normalized.startsWith("../")) {
781
+ throw new UnsafePathError("path must resolve inside the project");
782
+ }
783
+ for (const segment of normalized.split("/")) {
784
+ if (segment.length === 0 || segment.endsWith(".") || segment.endsWith(" ") || WINDOWS_RESERVED.test(segment) || hasIllegalWindowsCharacter(segment)) {
785
+ throw new UnsafePathError("path contains an illegal cross-platform segment");
786
+ }
787
+ }
788
+ return normalized;
789
+ }
790
+ function assertNoCaseCollisions(paths) {
791
+ const seen = /* @__PURE__ */ new Map();
792
+ for (const item of paths) {
793
+ const normalized = normalizeManagedPath(item);
794
+ const folded = normalized.toLocaleLowerCase("en-US");
795
+ const existing = seen.get(folded);
796
+ if (existing !== void 0 && existing !== normalized) {
797
+ throw new UnsafePathError("case-insensitive path collision: " + existing + " and " + normalized);
798
+ }
799
+ seen.set(folded, normalized);
800
+ }
801
+ }
802
+ function volumeRoot(path) {
803
+ const windowsRoot = win32.parse(path).root;
804
+ if (windowsRoot !== "") {
805
+ return windowsRoot.toLocaleLowerCase("en-US");
806
+ }
807
+ return resolve(path).slice(0, 1);
808
+ }
809
+ function assertSameVolume(first, second) {
810
+ if (volumeRoot(first) !== volumeRoot(second)) {
811
+ throw new UnsafePathError("atomic rename requires the same filesystem volume");
812
+ }
813
+ }
814
+ async function assertNoSymlinks(root, relativePath) {
815
+ const normalized = normalizeManagedPath(relativePath);
816
+ let current = resolve(root);
817
+ for (const segment of normalized.split("/")) {
818
+ current = join(current, segment);
819
+ try {
820
+ const stat5 = await lstat(current);
821
+ if (stat5.isSymbolicLink()) {
822
+ throw new UnsafePathError("symbolic links are not managed");
823
+ }
824
+ } catch (error) {
825
+ if (error instanceof UnsafePathError || !(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
826
+ throw error;
827
+ }
828
+ break;
829
+ }
830
+ }
831
+ }
832
+
833
+ // ../core/dist/api/retry.js
834
+ async function withRetry(action, options) {
835
+ const attempts = options.attempts ?? 3;
836
+ const sleep = options.sleep ?? (async (milliseconds) => {
837
+ await new Promise((resolve6) => setTimeout(resolve6, milliseconds));
838
+ });
839
+ let lastError;
840
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
841
+ try {
842
+ return await action(attempt);
843
+ } catch (error) {
844
+ lastError = error;
845
+ if (attempt === attempts || !options.shouldRetry(error, attempt)) {
846
+ throw error;
847
+ }
848
+ await sleep((options.baseDelayMs ?? 100) * 2 ** (attempt - 1));
849
+ }
850
+ }
851
+ throw lastError;
852
+ }
853
+
854
+ // ../core/dist/api/client.js
855
+ var ApiError = class extends Error {
856
+ status;
857
+ code;
858
+ requestId;
859
+ details;
860
+ constructor(status, code, message, requestId, details) {
861
+ super(message);
862
+ this.name = "ApiError";
863
+ this.status = status;
864
+ this.code = code;
865
+ this.requestId = requestId;
866
+ this.details = details;
867
+ }
868
+ };
869
+ var RetryableResponseError = class extends Error {
870
+ response;
871
+ constructor(response) {
872
+ super("retryable HTTP response: " + response.status);
873
+ this.response = response;
874
+ }
875
+ };
876
+ var HunterHarnessApiClient = class {
877
+ serverUrl;
878
+ fetch;
879
+ token;
880
+ sleep;
881
+ retryAttempts;
882
+ constructor(options) {
883
+ const url = new URL(options.serverUrl);
884
+ if (url.protocol !== "https:") {
885
+ throw new Error("server URL must use HTTPS");
886
+ }
887
+ if (options.token.trim() === "") {
888
+ throw new Error("API token is required");
889
+ }
890
+ this.serverUrl = url.toString().replace(/\/$/, "");
891
+ this.token = options.token;
892
+ this.fetch = options.fetch ?? globalThis.fetch;
893
+ this.sleep = options.sleep;
894
+ this.retryAttempts = options.retryAttempts ?? 3;
895
+ }
896
+ async request(method, path, options) {
897
+ const headers = new Headers({
898
+ Accept: "application/json",
899
+ Authorization: "Bearer " + this.token,
900
+ "X-Request-Id": options.requestId,
901
+ ...options.headers
902
+ });
903
+ if (options.idempotencyKey !== void 0) {
904
+ headers.set("Idempotency-Key", options.idempotencyKey);
905
+ }
906
+ let body;
907
+ if (options.rawBody !== void 0) {
908
+ body = options.rawBody;
909
+ } else if (options.body !== void 0) {
910
+ headers.set("Content-Type", "application/json");
911
+ body = JSON.stringify(options.body);
912
+ }
913
+ let response;
914
+ try {
915
+ response = await withRetry(async () => {
916
+ const value = await this.fetch(this.serverUrl + path, {
917
+ method,
918
+ headers,
919
+ ...body === void 0 ? {} : { body }
920
+ });
921
+ if (value.status === 429 || value.status >= 500) {
922
+ throw new RetryableResponseError(value);
923
+ }
924
+ return value;
925
+ }, {
926
+ attempts: this.retryAttempts,
927
+ ...this.sleep === void 0 ? {} : { sleep: this.sleep },
928
+ shouldRetry: () => true
929
+ });
930
+ } catch (error) {
931
+ if (error instanceof RetryableResponseError) {
932
+ response = error.response;
933
+ } else {
934
+ throw error;
935
+ }
936
+ }
937
+ const text = await response.text();
938
+ const payload = text === "" ? {} : JSON.parse(text);
939
+ if (!response.ok) {
940
+ const envelope = payload.error;
941
+ throw new ApiError(response.status, typeof envelope?.code === "string" ? envelope.code : "HTTP_ERROR", typeof envelope?.message === "string" ? envelope.message : "server request failed", typeof envelope?.request_id === "string" ? envelope.request_id : null, envelope?.details ?? {});
942
+ }
943
+ return payload;
944
+ }
945
+ async resolveProject(body, requestId, idempotencyKey) {
946
+ return this.request("POST", "/api/v1/projects:resolve", {
947
+ requestId,
948
+ idempotencyKey,
949
+ body
950
+ });
951
+ }
952
+ async createProposalSession(projectId, body, requestId, idempotencyKey) {
953
+ return this.request("POST", "/api/v1/projects/" + encodeURIComponent(projectId) + "/proposal-sessions", { requestId, idempotencyKey, body });
954
+ }
955
+ async queryBlobs(sessionId, hashes, requestId, idempotencyKey) {
956
+ return this.request("POST", "/api/v1/proposal-sessions/" + encodeURIComponent(sessionId) + "/blobs:query", { requestId, idempotencyKey, body: { content_sha256: hashes } });
957
+ }
958
+ async uploadBlobChunk(options) {
959
+ const end = options.start + options.chunk.byteLength - 1;
960
+ return this.request("PUT", "/api/v1/proposal-sessions/" + encodeURIComponent(options.sessionId) + "/blobs/" + encodeURIComponent(options.contentSha256), {
961
+ requestId: options.requestId,
962
+ idempotencyKey: options.idempotencyKey,
963
+ rawBody: options.chunk,
964
+ headers: {
965
+ "Content-Type": "application/octet-stream",
966
+ "Content-Range": "bytes " + options.start + "-" + end + "/" + options.total,
967
+ "X-Chunk-SHA256": sha256Bytes(options.chunk)
968
+ }
969
+ });
970
+ }
971
+ async finalizeProposal(sessionId, body, requestId, idempotencyKey) {
972
+ return this.request("POST", "/api/v1/proposal-sessions/" + encodeURIComponent(sessionId) + ":finalize", { requestId, idempotencyKey, body });
973
+ }
974
+ async getUpdateManifest(projectId, query, requestId) {
975
+ const parameters = new URLSearchParams({
976
+ base_project_version: query.base_project_version ?? "",
977
+ base_manifest_hash: query.base_manifest_hash,
978
+ adapter: query.adapter,
979
+ profile: query.profile
980
+ });
981
+ return this.request("GET", "/api/v1/projects/" + encodeURIComponent(projectId) + "/update-manifest?" + parameters.toString(), { requestId });
982
+ }
983
+ async getArtifactManifest(artifactId, requestId) {
984
+ return this.request("GET", "/api/v1/artifacts/" + encodeURIComponent(artifactId) + "/manifest", { requestId });
985
+ }
986
+ async downloadArtifactBlob(artifactId, contentSha256, requestId) {
987
+ const response = await withRetry(async () => {
988
+ const value = await this.fetch(this.serverUrl + "/api/v1/artifacts/" + encodeURIComponent(artifactId) + "/blobs/" + encodeURIComponent(contentSha256), {
989
+ headers: {
990
+ Accept: "application/octet-stream",
991
+ Authorization: "Bearer " + this.token,
992
+ "X-Request-Id": requestId
993
+ }
994
+ });
995
+ if (value.status === 429 || value.status >= 500) {
996
+ throw new RetryableResponseError(value);
997
+ }
998
+ return value;
999
+ }, {
1000
+ attempts: this.retryAttempts,
1001
+ ...this.sleep === void 0 ? {} : { sleep: this.sleep },
1002
+ shouldRetry: () => true
1003
+ });
1004
+ if (!response.ok) {
1005
+ const payload = await response.json();
1006
+ throw new ApiError(response.status, payload.error?.code ?? "HTTP_ERROR", payload.error?.message ?? "artifact download failed", payload.error?.request_id ?? null, payload.error?.details ?? {});
1007
+ }
1008
+ const bytes = new Uint8Array(await response.arrayBuffer());
1009
+ if (response.headers.get("X-Content-SHA256") !== contentSha256 || sha256Bytes(bytes) !== contentSha256) {
1010
+ throw new ApiError(422, "ARTIFACT_HASH_MISMATCH", "artifact blob integrity check failed", response.headers.get("X-Request-Id"), {});
1011
+ }
1012
+ return bytes;
1013
+ }
1014
+ };
1015
+
1016
+ // ../core/dist/knowledge/frontmatter.js
1017
+ import { parse as parseYaml } from "yaml";
1018
+
1019
+ // ../core/dist/state/atomic.js
1020
+ import { mkdir, rename, rm, writeFile } from "node:fs/promises";
1021
+ import { dirname } from "node:path";
1022
+ import { randomUUID } from "node:crypto";
1023
+ async function atomicWriteFile(target, content) {
1024
+ await mkdir(dirname(target), { recursive: true });
1025
+ const temporary = target + ".tmp-" + randomUUID();
1026
+ await writeFile(temporary, content, { flag: "wx" });
1027
+ try {
1028
+ await rename(temporary, target);
1029
+ } catch {
1030
+ await rm(target, { force: true });
1031
+ await rename(temporary, target);
1032
+ }
1033
+ }
1034
+ async function atomicWriteJson(target, value) {
1035
+ await atomicWriteFile(target, JSON.stringify(value, null, 2) + "\n");
1036
+ }
1037
+
1038
+ // ../core/dist/knowledge/index.js
1039
+ var MAX_ENTRY_BYTES = 64 * 1024;
1040
+
1041
+ // ../core/dist/managed/managed-block.js
1042
+ var MANAGED_BLOCK_START = "<!-- hunter-harness:start -->";
1043
+ var MANAGED_BLOCK_END = "<!-- hunter-harness:end -->";
1044
+ function markerCount(content, marker) {
1045
+ return content.split(marker).length - 1;
1046
+ }
1047
+ function validateMarkers(content) {
1048
+ const starts = markerCount(content, MANAGED_BLOCK_START);
1049
+ const ends = markerCount(content, MANAGED_BLOCK_END);
1050
+ if (starts === 0 && ends === 0) {
1051
+ return "absent";
1052
+ }
1053
+ if (starts !== 1 || ends !== 1) {
1054
+ throw new Error("managed block markers are malformed or duplicated");
1055
+ }
1056
+ if (content.indexOf(MANAGED_BLOCK_START) > content.indexOf(MANAGED_BLOCK_END)) {
1057
+ throw new Error("managed block markers are out of order");
1058
+ }
1059
+ return "present";
1060
+ }
1061
+ function renderBlock(content, newline) {
1062
+ const normalized = content.replace(/\r\n/g, "\n").replace(/\n/g, newline);
1063
+ return MANAGED_BLOCK_START + newline + normalized + newline + MANAGED_BLOCK_END;
1064
+ }
1065
+ function extractManagedBlock(content) {
1066
+ if (validateMarkers(content) === "absent") {
1067
+ return null;
1068
+ }
1069
+ const start = content.indexOf(MANAGED_BLOCK_START) + MANAGED_BLOCK_START.length;
1070
+ const end = content.indexOf(MANAGED_BLOCK_END);
1071
+ return content.slice(start, end).replace(/^\r?\n/, "").replace(/\r?\n$/, "");
1072
+ }
1073
+ function upsertManagedBlock(original, content) {
1074
+ const newline = original.includes("\r\n") ? "\r\n" : "\n";
1075
+ const block = renderBlock(content, newline);
1076
+ if (validateMarkers(original) === "absent") {
1077
+ if (original.length === 0) {
1078
+ return block + newline;
1079
+ }
1080
+ const separator = original.endsWith(newline) ? newline : newline + newline;
1081
+ return original + separator + block + newline;
1082
+ }
1083
+ const start = original.indexOf(MANAGED_BLOCK_START);
1084
+ const end = original.indexOf(MANAGED_BLOCK_END) + MANAGED_BLOCK_END.length;
1085
+ return original.slice(0, start) + block + original.slice(end);
1086
+ }
1087
+ function removeManagedBlock(original) {
1088
+ if (validateMarkers(original) === "absent") {
1089
+ return original;
1090
+ }
1091
+ const start = original.indexOf(MANAGED_BLOCK_START);
1092
+ const end = original.indexOf(MANAGED_BLOCK_END) + MANAGED_BLOCK_END.length;
1093
+ const before = original.slice(0, start).replace(/(?:\r?\n){2}$/, "\n");
1094
+ const after = original.slice(end).replace(/^(?:\r?\n){1,2}/, "");
1095
+ return before + after;
1096
+ }
1097
+ function escapeRe(value) {
1098
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1099
+ }
1100
+ var startById = (id) => `<!-- hunter-harness:start id=${id} -->`;
1101
+ var endById = (id) => `<!-- hunter-harness:end id=${id} -->`;
1102
+ function upsertManagedBlockById(original, id, content) {
1103
+ const newline = original.includes("\r\n") ? "\r\n" : "\n";
1104
+ const block = startById(id) + newline + content + newline + endById(id);
1105
+ const re = new RegExp(escapeRe(startById(id)) + "[\\s\\S]*?" + escapeRe(endById(id)));
1106
+ if (re.test(original)) {
1107
+ return original.replace(re, block);
1108
+ }
1109
+ const separator = original.length === 0 ? "" : original.endsWith(newline) ? newline : newline + newline;
1110
+ return original + separator + block + newline;
1111
+ }
1112
+
1113
+ // ../core/dist/policy/file-policy.js
1114
+ var USER_MANAGED_BLOCK = {
1115
+ file_kind: "user_editable",
1116
+ edit_policy: "managed-block-only",
1117
+ push_policy: "diff-proposal",
1118
+ update_policy: "managed-block-only",
1119
+ conflict_policy: "managed-block-skip"
1120
+ };
1121
+ var USER_DIFF = {
1122
+ file_kind: "user_editable",
1123
+ edit_policy: "allow",
1124
+ push_policy: "diff-proposal",
1125
+ update_policy: "skip-if-local-dirty",
1126
+ conflict_policy: "skip-and-report"
1127
+ };
1128
+ var USER_FULL = {
1129
+ file_kind: "user_editable",
1130
+ edit_policy: "allow",
1131
+ push_policy: "full-diff-proposal",
1132
+ update_policy: "skip-if-local-dirty",
1133
+ conflict_policy: "skip-and-report"
1134
+ };
1135
+ var PROJECT_LOCAL = {
1136
+ file_kind: "user_editable",
1137
+ edit_policy: "allow",
1138
+ push_policy: "confirm-before-proposal",
1139
+ update_policy: "never",
1140
+ conflict_policy: "ignore"
1141
+ };
1142
+ var GENERATED_REVIEWABLE = {
1143
+ file_kind: "generated_reviewable",
1144
+ edit_policy: "discourage",
1145
+ push_policy: "full-diff-proposal",
1146
+ update_policy: "skip-if-local-dirty",
1147
+ conflict_policy: "skip-and-report"
1148
+ };
1149
+ var CONTEXT_INDEX = {
1150
+ file_kind: "generated_reviewable",
1151
+ edit_policy: "discourage",
1152
+ push_policy: "diff-proposal",
1153
+ update_policy: "replace-if-baseline-clean",
1154
+ conflict_policy: "skip-and-report"
1155
+ };
1156
+ var INTERNAL_STATE = {
1157
+ file_kind: "internal_state",
1158
+ edit_policy: "protocol-only",
1159
+ push_policy: "never",
1160
+ update_policy: "protocol-only",
1161
+ conflict_policy: "protocol-recover"
1162
+ };
1163
+ var GENERATED_CACHE = {
1164
+ file_kind: "generated_cache",
1165
+ edit_policy: "protocol-only",
1166
+ push_policy: "never",
1167
+ update_policy: "protocol-rebuild-only",
1168
+ conflict_policy: "protocol-recover"
1169
+ };
1170
+ var REPORT_CACHE = {
1171
+ file_kind: "generated_cache",
1172
+ edit_policy: "discourage",
1173
+ push_policy: "never",
1174
+ update_policy: "never",
1175
+ conflict_policy: "ignore"
1176
+ };
1177
+ var EXTERNAL = {
1178
+ file_kind: "external_unmanaged",
1179
+ edit_policy: "external",
1180
+ push_policy: "never",
1181
+ update_policy: "never",
1182
+ conflict_policy: "ignore"
1183
+ };
1184
+ function under(path, prefix) {
1185
+ return path === prefix.slice(0, -1) || path.startsWith(prefix);
1186
+ }
1187
+ function classifyFile(input) {
1188
+ const path = normalizeManagedPath(input);
1189
+ if (path === "CLAUDE.md" || path === "AGENTS.md") {
1190
+ return USER_MANAGED_BLOCK;
1191
+ }
1192
+ if (under(path, ".claude/rules/") || under(path, ".claude/skills/harness-")) {
1193
+ return USER_DIFF;
1194
+ }
1195
+ if (under(path, ".cursor/rules/") || under(path, ".agent-skills/")) {
1196
+ return USER_DIFF;
1197
+ }
1198
+ if (under(path, ".harness/knowledge/project-local/")) {
1199
+ return PROJECT_LOCAL;
1200
+ }
1201
+ if (under(path, ".harness/knowledge/")) {
1202
+ return USER_FULL;
1203
+ }
1204
+ if (under(path, ".harness/codebase/map/") || path === ".harness/codebase/map-summary.md" || path === ".harness/codebase/map-manifest.json") {
1205
+ return GENERATED_REVIEWABLE;
1206
+ }
1207
+ if (path === ".harness/context-index.json") {
1208
+ return CONTEXT_INDEX;
1209
+ }
1210
+ if (under(path, ".harness/reports/")) {
1211
+ return REPORT_CACHE;
1212
+ }
1213
+ if (under(path, ".harness/state/") || under(path, ".harness/rules/")) {
1214
+ return INTERNAL_STATE;
1215
+ }
1216
+ if (under(path, ".harness/generated/") || under(path, ".harness/cache/")) {
1217
+ return GENERATED_CACHE;
1218
+ }
1219
+ if (path === ".harness/project.yaml") {
1220
+ return USER_DIFF;
1221
+ }
1222
+ return EXTERNAL;
1223
+ }
1224
+ function decidePush(policy, confirmed) {
1225
+ if (policy.push_policy === "never") {
1226
+ return { include: false, reason: "policy-never" };
1227
+ }
1228
+ if (policy.push_policy === "confirm-before-proposal" && !confirmed) {
1229
+ return { include: false, reason: "confirmation-required" };
1230
+ }
1231
+ return { include: true };
1232
+ }
1233
+ function decideUpdate(policy, dirty) {
1234
+ if (policy.update_policy === "never") {
1235
+ return { apply: false, reason: "policy-never" };
1236
+ }
1237
+ if (policy.update_policy === "protocol-only" || policy.update_policy === "protocol-rebuild-only") {
1238
+ return { apply: false, reason: "protocol-only" };
1239
+ }
1240
+ if (dirty) {
1241
+ return { apply: false, reason: "local-dirty" };
1242
+ }
1243
+ return { apply: true };
1244
+ }
1245
+
1246
+ // ../core/dist/project/initialize.js
1247
+ import { createHash as createHash2 } from "node:crypto";
1248
+ import { readFile as readFile3, stat as stat2 } from "node:fs/promises";
1249
+ import { basename, join as join4, resolve as resolve3 } from "node:path";
1250
+ import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
1251
+
1252
+ // ../core/dist/transaction/transaction.js
1253
+ import { randomUUID as randomUUID2 } from "node:crypto";
1254
+ import { copyFile, mkdir as mkdir3, readFile as readFile2, rename as rename2, rm as rm2, stat, writeFile as writeFile2 } from "node:fs/promises";
1255
+ import { dirname as dirname2, join as join3 } from "node:path";
1256
+
1257
+ // ../core/dist/state/layout.js
1258
+ import { mkdir as mkdir2 } from "node:fs/promises";
1259
+ import { join as join2, resolve as resolve2 } from "node:path";
1260
+ function stateLayout(projectRoot) {
1261
+ const root = resolve2(projectRoot);
1262
+ const harness = join2(root, ".harness");
1263
+ return {
1264
+ root,
1265
+ harness,
1266
+ baseline: join2(harness, "state", "baseline"),
1267
+ transactions: join2(harness, "state", "transactions"),
1268
+ locks: join2(harness, "state", "locks"),
1269
+ local: join2(harness, "state", "local"),
1270
+ serverArtifacts: join2(harness, "cache", "server-artifacts"),
1271
+ reports: join2(harness, "reports")
1272
+ };
1273
+ }
1274
+ async function ensureStateLayout(projectRoot) {
1275
+ const layout = stateLayout(projectRoot);
1276
+ await Promise.all([
1277
+ mkdir2(layout.baseline, { recursive: true }),
1278
+ mkdir2(layout.transactions, { recursive: true }),
1279
+ mkdir2(layout.locks, { recursive: true }),
1280
+ mkdir2(layout.local, { recursive: true }),
1281
+ mkdir2(layout.serverArtifacts, { recursive: true }),
1282
+ mkdir2(layout.reports, { recursive: true })
1283
+ ]);
1284
+ return layout;
1285
+ }
1286
+
1287
+ // ../core/dist/transaction/transaction.js
1288
+ var InterruptedTransactionError = class extends Error {
1289
+ constructor() {
1290
+ super("transaction interrupted by failure injection");
1291
+ this.name = "InterruptedTransactionError";
1292
+ }
1293
+ };
1294
+ function encodePath(path) {
1295
+ return Buffer.from(path).toString("base64url");
1296
+ }
1297
+ async function exists(path) {
1298
+ try {
1299
+ await stat(path);
1300
+ return true;
1301
+ } catch (error) {
1302
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1303
+ return false;
1304
+ }
1305
+ throw error;
1306
+ }
1307
+ }
1308
+ function affectedPaths(operation) {
1309
+ if (operation.operation === "rename") {
1310
+ return [operation.from_path, operation.to_path];
1311
+ }
1312
+ return [operation.path];
1313
+ }
1314
+ async function writeJournal(transactionRoot, journal) {
1315
+ await atomicWriteJson(join3(transactionRoot, "journal.json"), journal);
1316
+ await atomicWriteJson(join3(transactionRoot, "status.json"), {
1317
+ schema_version: 1,
1318
+ transaction_id: journal.transaction_id,
1319
+ state: journal.state,
1320
+ applied_count: journal.applied_count,
1321
+ failure: journal.failure,
1322
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
1323
+ });
1324
+ }
1325
+ async function snapshotPaths(projectRoot, transactionRoot, paths) {
1326
+ const snapshots = [];
1327
+ for (const path of paths) {
1328
+ const target = join3(projectRoot, path);
1329
+ const present = await exists(target);
1330
+ const snapshotName = present ? encodePath(path) : null;
1331
+ if (snapshotName !== null) {
1332
+ await copyFile(target, join3(transactionRoot, "before", snapshotName));
1333
+ }
1334
+ snapshots.push({ path, existed: present, snapshot_name: snapshotName });
1335
+ }
1336
+ return snapshots;
1337
+ }
1338
+ async function stageOperations(transactionRoot, operations) {
1339
+ for (let index = 0; index < operations.length; index += 1) {
1340
+ const operation = operations[index];
1341
+ if (operation?.operation === "add" || operation?.operation === "modify" || operation?.operation === "rename") {
1342
+ await writeFile2(join3(transactionRoot, "staged", String(index)), operation.content);
1343
+ }
1344
+ }
1345
+ }
1346
+ async function installStaged(staged, target, transactionId) {
1347
+ await mkdir3(dirname2(target), { recursive: true });
1348
+ assertSameVolume(staged, target);
1349
+ const temporary = join3(dirname2(target), ".hunter-" + transactionId + "-" + randomUUID2() + ".tmp");
1350
+ await copyFile(staged, temporary);
1351
+ await rm2(target, { force: true });
1352
+ await rename2(temporary, target);
1353
+ }
1354
+ async function applyOperation(projectRoot, transactionRoot, operation, index, transactionId) {
1355
+ if (operation.operation === "delete") {
1356
+ await rm2(join3(projectRoot, operation.path), { force: true });
1357
+ return;
1358
+ }
1359
+ if (operation.operation === "rename") {
1360
+ await rm2(join3(projectRoot, operation.from_path), { force: true });
1361
+ await installStaged(join3(transactionRoot, "staged", String(index)), join3(projectRoot, operation.to_path), transactionId);
1362
+ return;
1363
+ }
1364
+ await installStaged(join3(transactionRoot, "staged", String(index)), join3(projectRoot, operation.path), transactionId);
1365
+ }
1366
+ async function rollbackTransaction(projectRoot, transactionId) {
1367
+ const transactionRoot = join3(stateLayout(projectRoot).transactions, transactionId);
1368
+ const journal = JSON.parse(await readFile2(join3(transactionRoot, "journal.json"), "utf8"));
1369
+ if (journal.state === "committed") {
1370
+ return { transactionId, status: "committed" };
1371
+ }
1372
+ journal.state = "rolling_back";
1373
+ await writeJournal(transactionRoot, journal);
1374
+ try {
1375
+ for (const snapshot of [...journal.snapshots].reverse()) {
1376
+ const target = join3(projectRoot, snapshot.path);
1377
+ await rm2(target, { force: true, recursive: true });
1378
+ if (snapshot.existed && snapshot.snapshot_name !== null) {
1379
+ await mkdir3(dirname2(target), { recursive: true });
1380
+ await copyFile(join3(transactionRoot, "before", snapshot.snapshot_name), target);
1381
+ }
1382
+ }
1383
+ journal.state = "rolled_back";
1384
+ await writeJournal(transactionRoot, journal);
1385
+ return { transactionId, status: "rolled_back" };
1386
+ } catch (error) {
1387
+ journal.state = "recovery_required";
1388
+ journal.failure = error instanceof Error ? error.message : String(error);
1389
+ await writeJournal(transactionRoot, journal);
1390
+ throw error;
1391
+ }
1392
+ }
1393
+ async function runTransaction(projectRoot, rawOperations, options = {}) {
1394
+ const layout = await ensureStateLayout(projectRoot);
1395
+ const transactionId = options.id ?? "tx_" + Date.now() + "_" + randomUUID2();
1396
+ const transactionRoot = join3(layout.transactions, transactionId);
1397
+ await Promise.all([
1398
+ mkdir3(join3(transactionRoot, "before"), { recursive: true }),
1399
+ mkdir3(join3(transactionRoot, "after"), { recursive: true }),
1400
+ mkdir3(join3(transactionRoot, "staged"), { recursive: true })
1401
+ ]);
1402
+ const operations = rawOperations.map((operation) => {
1403
+ if (operation.operation === "rename") {
1404
+ return {
1405
+ ...operation,
1406
+ from_path: normalizeManagedPath(operation.from_path),
1407
+ to_path: normalizeManagedPath(operation.to_path)
1408
+ };
1409
+ }
1410
+ return { ...operation, path: normalizeManagedPath(operation.path) };
1411
+ });
1412
+ const paths = operations.flatMap(affectedPaths);
1413
+ assertNoCaseCollisions(paths);
1414
+ for (const path of paths) {
1415
+ await assertNoSymlinks(projectRoot, path);
1416
+ }
1417
+ const snapshots = await snapshotPaths(projectRoot, transactionRoot, paths);
1418
+ await stageOperations(transactionRoot, operations);
1419
+ const journal = {
1420
+ schema_version: 1,
1421
+ transaction_id: transactionId,
1422
+ ...options.kind === void 0 ? {} : { kind: options.kind },
1423
+ state: "prepared",
1424
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1425
+ operations,
1426
+ snapshots,
1427
+ applied_count: 0,
1428
+ failure: null
1429
+ };
1430
+ await writeJournal(transactionRoot, journal);
1431
+ try {
1432
+ journal.state = "applying";
1433
+ await writeJournal(transactionRoot, journal);
1434
+ for (let index = 0; index < operations.length; index += 1) {
1435
+ const operation = operations[index];
1436
+ if (operation === void 0) {
1437
+ continue;
1438
+ }
1439
+ await applyOperation(projectRoot, transactionRoot, operation, index, transactionId);
1440
+ journal.applied_count = index + 1;
1441
+ await writeJournal(transactionRoot, journal);
1442
+ if (options.interruptAfterApply === journal.applied_count) {
1443
+ journal.state = "interrupted";
1444
+ journal.failure = "injected interruption";
1445
+ await writeJournal(transactionRoot, journal);
1446
+ throw new InterruptedTransactionError();
1447
+ }
1448
+ if (options.failAfterApply === journal.applied_count) {
1449
+ throw new Error("injected transaction failure");
1450
+ }
1451
+ }
1452
+ const after = [];
1453
+ for (const path of paths) {
1454
+ const target = join3(projectRoot, path);
1455
+ after.push({
1456
+ path,
1457
+ exists: await exists(target),
1458
+ hash: await exists(target) ? await sha256File(target) : null
1459
+ });
1460
+ }
1461
+ await atomicWriteJson(join3(transactionRoot, "after", "manifest.json"), after);
1462
+ journal.state = "committed";
1463
+ await writeJournal(transactionRoot, journal);
1464
+ return { transactionId, status: "committed" };
1465
+ } catch (error) {
1466
+ if (error instanceof InterruptedTransactionError) {
1467
+ throw error;
1468
+ }
1469
+ journal.failure = error instanceof Error ? error.message : String(error);
1470
+ await writeJournal(transactionRoot, journal);
1471
+ await rollbackTransaction(projectRoot, transactionId);
1472
+ throw error;
1473
+ }
1474
+ }
1475
+
1476
+ // ../core/dist/project/uuid-v7.js
1477
+ import { randomBytes } from "node:crypto";
1478
+ function uuidV7(now = Date.now()) {
1479
+ const bytes = randomBytes(16);
1480
+ const timestamp = BigInt(now);
1481
+ for (let index = 5; index >= 0; index -= 1) {
1482
+ bytes[index] = Number(timestamp >> BigInt((5 - index) * 8) & 0xffn);
1483
+ }
1484
+ const byte6 = bytes[6] ?? 0;
1485
+ const byte8 = bytes[8] ?? 0;
1486
+ bytes[6] = byte6 & 15 | 112;
1487
+ bytes[8] = byte8 & 63 | 128;
1488
+ const hex = bytes.toString("hex");
1489
+ return [
1490
+ hex.slice(0, 8),
1491
+ hex.slice(8, 12),
1492
+ hex.slice(12, 16),
1493
+ hex.slice(16, 20),
1494
+ hex.slice(20)
1495
+ ].join("-");
1496
+ }
1497
+
1498
+ // ../core/dist/project/initialize.js
1499
+ async function readOptional(path) {
1500
+ try {
1501
+ return await readFile3(path, "utf8");
1502
+ } catch (error) {
1503
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1504
+ return "";
1505
+ }
1506
+ throw error;
1507
+ }
1508
+ }
1509
+ async function exists2(path) {
1510
+ try {
1511
+ await stat2(path);
1512
+ return true;
1513
+ } catch (error) {
1514
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1515
+ return false;
1516
+ }
1517
+ throw error;
1518
+ }
1519
+ }
1520
+ async function existingProjectConfig(root) {
1521
+ const path = join4(root, ".harness", "project.yaml");
1522
+ const content = await readOptional(path);
1523
+ return content === "" ? null : projectConfigSchema.parse(parseYaml2(content));
1524
+ }
1525
+ async function operationFor(root, path, content) {
1526
+ return await exists2(join4(root, path)) ? { operation: "modify", path, content } : { operation: "add", path, content };
1527
+ }
1528
+ async function readProfileBundle(resourcesRoot, profile) {
1529
+ const manifest = JSON.parse(await readFile3(join4(resourcesRoot, "harness", "manifests", `${profile}.json`), "utf8"));
1530
+ if (manifest.schema_version !== 1 || manifest.profile !== profile) {
1531
+ throw new Error(`invalid ${profile} Harness Bundle manifest`);
1532
+ }
1533
+ const files = /* @__PURE__ */ new Map();
1534
+ for (const item of manifest.files) {
1535
+ const bytes = await readFile3(join4(resourcesRoot, "harness", profile, item.path));
1536
+ if (createHash2("sha256").update(bytes).digest("hex") !== item.sha256) {
1537
+ throw new Error(`Harness Bundle hash mismatch: ${item.path}`);
1538
+ }
1539
+ files.set(item.path, bytes);
1540
+ }
1541
+ return { manifest, files };
1542
+ }
1543
+ function bundleTargets(bundleFiles) {
1544
+ const targets = /* @__PURE__ */ new Map();
1545
+ for (const [path, bytes] of bundleFiles) {
1546
+ targets.set(`.claude/skills/${path}`, bytes);
1547
+ const agent = /^agents\/([^/]+\.md)$/.exec(path);
1548
+ if (agent?.[1] !== void 0)
1549
+ targets.set(`.claude/agents/${agent[1]}`, bytes);
1550
+ }
1551
+ return targets;
1552
+ }
1553
+ var INSTALLED_BUNDLE_PATH = ".harness/state/local/installed-harness-bundle.json";
1554
+ async function previousInstalledFiles(root) {
1555
+ const content = await readOptional(join4(root, INSTALLED_BUNDLE_PATH));
1556
+ if (content === "")
1557
+ return [];
1558
+ const parsed = JSON.parse(content);
1559
+ return parsed.schema_version === 1 && Array.isArray(parsed.files) ? parsed.files : [];
1560
+ }
1561
+ async function initializeProject(options) {
1562
+ const root = resolve3(options.projectRoot);
1563
+ const config = initConfigSchema.parse(options.config);
1564
+ const existing = await existingProjectConfig(root);
1565
+ const profile = config.profile;
1566
+ const bundle = await readProfileBundle(options.resourcesRoot, profile);
1567
+ const bundleFiles = bundleTargets(bundle.files);
1568
+ const bundleHash = sha256Bytes(canonicalJson(bundle.manifest.files));
1569
+ const projectConfig = projectConfigSchema.parse({
1570
+ harness: { name: "hunter-harness", schema_version: 1 },
1571
+ project: {
1572
+ name: existing?.project.name ?? basename(root),
1573
+ root: ".",
1574
+ local_project_key: existing?.project.local_project_key ?? uuidV7(),
1575
+ project_id: config.project_id ?? existing?.project.project_id ?? null,
1576
+ profiles: [config.profile]
1577
+ },
1578
+ server: {
1579
+ url: config.server_url ?? existing?.server.url ?? null,
1580
+ token_env: config.token_env ?? existing?.server.token_env ?? "HUNTER_HARNESS_TOKEN"
1581
+ },
1582
+ adapters: { enabled: [config.adapter] }
1583
+ });
1584
+ const baseline = baselineManifestSchema.parse({
1585
+ schema_version: 1,
1586
+ project_id: projectConfig.project.project_id,
1587
+ complete_project_version: null,
1588
+ artifact_manifest_hash: null,
1589
+ files: {}
1590
+ });
1591
+ const agentsContent = upsertManagedBlock(await readOptional(join4(root, "AGENTS.md")), [
1592
+ "# Hunter Harness",
1593
+ "",
1594
+ "Use .harness/context-index.json to route rules, Knowledge, and codebase maps.",
1595
+ "Treat .claude/skills/harness-* as editable adapter working copies.",
1596
+ "Do not modify .harness/state or .harness/cache directly."
1597
+ ].join("\n"));
1598
+ const claudeContent = upsertManagedBlock(await readOptional(join4(root, "CLAUDE.md")), [
1599
+ "@AGENTS.md",
1600
+ "",
1601
+ "# Hunter Harness",
1602
+ "",
1603
+ "- Rules: .claude/rules/",
1604
+ "- Skills: .claude/skills/harness-*/",
1605
+ "- Knowledge: .harness/knowledge/",
1606
+ "- Codebase map: .harness/codebase/map/"
1607
+ ].join("\n"));
1608
+ const files = /* @__PURE__ */ new Map([
1609
+ [
1610
+ ".harness/project.yaml",
1611
+ stringifyYaml(projectConfig, { sortMapEntries: true })
1612
+ ],
1613
+ [
1614
+ ".harness/state/baseline/manifest.json",
1615
+ JSON.stringify(baseline, null, 2) + "\n"
1616
+ ],
1617
+ [
1618
+ ".harness/context-index.json",
1619
+ JSON.stringify({
1620
+ schema_version: 1,
1621
+ project: { claude_md: "CLAUDE.md", agents_md: "AGENTS.md" },
1622
+ rules: [".claude/rules/harness-general.md"],
1623
+ knowledge: { index: ".harness/knowledge/index.json" },
1624
+ codebase: { map: ".harness/codebase/map", status: "missing" },
1625
+ skill_bundle: {
1626
+ registry_version: bundle.manifest.bundle_version,
1627
+ bundle_hash: bundleHash
1628
+ }
1629
+ }, null, 2) + "\n"
1630
+ ],
1631
+ [
1632
+ ".harness/knowledge/index.json",
1633
+ JSON.stringify({ schema_version: 1, generated_at: null, entries: [] }, null, 2) + "\n"
1634
+ ],
1635
+ [".harness/knowledge/_candidates/.gitkeep", ""],
1636
+ [".harness/knowledge/project-local/.gitkeep", ""],
1637
+ [".harness/codebase/map/.gitkeep", ""],
1638
+ [".harness/state/local/.gitkeep", ""],
1639
+ [".harness/cache/server-artifacts/.gitkeep", ""],
1640
+ [".harness/reports/.gitkeep", ""],
1641
+ [
1642
+ ".harness/README.md",
1643
+ "# Hunter Harness\n\nUse npx hunter-harness, update, and push.\n"
1644
+ ],
1645
+ ["AGENTS.md", agentsContent],
1646
+ ["CLAUDE.md", claudeContent],
1647
+ [
1648
+ ".claude/rules/harness-general.md",
1649
+ "# Hunter Harness Rules\n\n- Report evidence honestly.\n- Do not execute destructive actions without confirmation.\n"
1650
+ ]
1651
+ ]);
1652
+ if (config.profile === "java") {
1653
+ files.set(".claude/rules/harness-profile-java.md", "# Java Profile\n\n- Verify builds and tests with the project build tool.\n");
1654
+ }
1655
+ for (const [targetPath, bytes] of bundleFiles) {
1656
+ files.set(targetPath, bytes);
1657
+ }
1658
+ const installedFileList = [...bundleFiles.keys()].sort();
1659
+ if (profile === "java")
1660
+ installedFileList.push(".claude/rules/harness-profile-java.md");
1661
+ const installedManifest = {
1662
+ schema_version: 1,
1663
+ profile,
1664
+ files: installedFileList.sort()
1665
+ };
1666
+ files.set(INSTALLED_BUNDLE_PATH, JSON.stringify(installedManifest, null, 2) + "\n");
1667
+ const newManaged = new Set(installedManifest.files);
1668
+ const deleteOperations = (await previousInstalledFiles(root)).filter((path) => !newManaged.has(path)).map((path) => ({ operation: "delete", path }));
1669
+ const paths = [...files.keys()].sort((left, right) => left.localeCompare(right));
1670
+ if (!options.dryRun) {
1671
+ const writeOperations = await Promise.all([...files.entries()].map(async ([path, content]) => operationFor(root, path, content)));
1672
+ await runTransaction(root, [...deleteOperations, ...writeOperations], { kind: "init" });
1673
+ }
1674
+ return {
1675
+ projectConfig,
1676
+ paths,
1677
+ bundleHash,
1678
+ registryVersion: bundle.manifest.bundle_version
1679
+ };
1680
+ }
1681
+
1682
+ // ../core/dist/proposal/diff.js
1683
+ function operationPath(operation) {
1684
+ return operation.operation === "rename" ? operation.to_path : operation.path;
1685
+ }
1686
+ function buildProposalDiff(input) {
1687
+ const confirmed = new Set(input.confirmedProjectLocal.map(normalizeManagedPath));
1688
+ const skipped = [];
1689
+ const current = /* @__PURE__ */ new Map();
1690
+ for (const [rawPath, content] of Object.entries(input.files)) {
1691
+ const path = normalizeManagedPath(rawPath);
1692
+ const policy = classifyFile(path);
1693
+ const decision = decidePush(policy, confirmed.has(path));
1694
+ if (!decision.include) {
1695
+ skipped.push({ path, reason: decision.reason });
1696
+ continue;
1697
+ }
1698
+ current.set(path, {
1699
+ path,
1700
+ content,
1701
+ hash: sha256Bytes(content),
1702
+ size: Buffer.byteLength(content),
1703
+ fileKind: policy.file_kind
1704
+ });
1705
+ }
1706
+ const baseline = /* @__PURE__ */ new Map();
1707
+ for (const [rawPath, entry] of Object.entries(input.baseline)) {
1708
+ const path = normalizeManagedPath(rawPath);
1709
+ const policy = classifyFile(path);
1710
+ const decision = decidePush(policy, confirmed.has(path));
1711
+ if (!decision.include) {
1712
+ if (!current.has(path)) {
1713
+ skipped.push({ path, reason: decision.reason });
1714
+ }
1715
+ continue;
1716
+ }
1717
+ baseline.set(path, entry);
1718
+ }
1719
+ const additions = [...current.values()].filter((entry) => !baseline.has(entry.path));
1720
+ const deletions = [...baseline.entries()].filter(([path]) => !current.has(path));
1721
+ const consumedAdditions = /* @__PURE__ */ new Set();
1722
+ const consumedDeletions = /* @__PURE__ */ new Set();
1723
+ const operations = [];
1724
+ const blobs = {};
1725
+ for (const [fromPath, previous] of deletions) {
1726
+ const renamed = additions.find((entry) => !consumedAdditions.has(entry.path) && entry.hash === previous.content_sha256 && entry.fileKind === classifyFile(fromPath).file_kind);
1727
+ if (renamed === void 0) {
1728
+ continue;
1729
+ }
1730
+ operations.push({
1731
+ operation: "rename",
1732
+ from_path: fromPath,
1733
+ to_path: renamed.path,
1734
+ file_kind: renamed.fileKind,
1735
+ base_content_sha256: previous.content_sha256,
1736
+ content_sha256: renamed.hash,
1737
+ size_bytes: renamed.size
1738
+ });
1739
+ blobs[renamed.hash] = renamed.content;
1740
+ consumedAdditions.add(renamed.path);
1741
+ consumedDeletions.add(fromPath);
1742
+ }
1743
+ for (const entry of current.values()) {
1744
+ const previous = baseline.get(entry.path);
1745
+ if (previous === void 0 || previous.content_sha256 === entry.hash) {
1746
+ if (previous !== void 0) {
1747
+ skipped.push({ path: entry.path, reason: "unchanged" });
1748
+ }
1749
+ continue;
1750
+ }
1751
+ operations.push({
1752
+ operation: "modify",
1753
+ path: entry.path,
1754
+ file_kind: entry.fileKind,
1755
+ base_content_sha256: previous.content_sha256,
1756
+ content_sha256: entry.hash,
1757
+ size_bytes: entry.size
1758
+ });
1759
+ blobs[entry.hash] = entry.content;
1760
+ }
1761
+ for (const entry of additions) {
1762
+ if (consumedAdditions.has(entry.path)) {
1763
+ continue;
1764
+ }
1765
+ operations.push({
1766
+ operation: "add",
1767
+ path: entry.path,
1768
+ file_kind: entry.fileKind,
1769
+ content_sha256: entry.hash,
1770
+ size_bytes: entry.size
1771
+ });
1772
+ blobs[entry.hash] = entry.content;
1773
+ }
1774
+ for (const [path, previous] of deletions) {
1775
+ if (consumedDeletions.has(path)) {
1776
+ continue;
1777
+ }
1778
+ operations.push({
1779
+ operation: "delete",
1780
+ path,
1781
+ file_kind: classifyFile(path).file_kind,
1782
+ base_content_sha256: previous.content_sha256,
1783
+ tombstone: {
1784
+ deleted_at: input.deletedAt,
1785
+ reason: input.deleteReason,
1786
+ previous_sha256: previous.content_sha256
1787
+ }
1788
+ });
1789
+ }
1790
+ operations.sort((left, right) => operationPath(left).localeCompare(operationPath(right)));
1791
+ skipped.sort((left, right) => left.path.localeCompare(right.path));
1792
+ return { operations, blobs, skipped };
1793
+ }
1794
+
1795
+ // ../core/dist/security/allowlist.js
1796
+ function parseInlineIgnores(content) {
1797
+ return [...content.matchAll(/hunter-harness-ignore:\s*([A-Z0-9_]+)\s+reason=([A-Za-z0-9._-]+)/g)].map((match) => ({
1798
+ ruleId: match[1] ?? "",
1799
+ reason: match[2] ?? "unspecified"
1800
+ }));
1801
+ }
1802
+
1803
+ // ../core/dist/security/entropy.js
1804
+ function shannonEntropy(value) {
1805
+ if (value.length === 0) {
1806
+ return 0;
1807
+ }
1808
+ const counts = /* @__PURE__ */ new Map();
1809
+ for (const character of value) {
1810
+ counts.set(character, (counts.get(character) ?? 0) + 1);
1811
+ }
1812
+ let entropy = 0;
1813
+ for (const count of counts.values()) {
1814
+ const probability = count / value.length;
1815
+ entropy -= probability * Math.log2(probability);
1816
+ }
1817
+ return entropy;
1818
+ }
1819
+ function highEntropyCandidates(content) {
1820
+ const matches = content.matchAll(/\b[A-Za-z0-9_+/.=-]{24,}\b/g);
1821
+ return [...matches].map((match) => ({
1822
+ value: match[0],
1823
+ offset: match.index,
1824
+ entropy: shannonEntropy(match[0])
1825
+ })).filter((item) => item.entropy >= 4.5);
1826
+ }
1827
+
1828
+ // ../core/dist/security/scanner.js
1829
+ var SENSITIVE_SCANNER_VERSION = "1.0.0";
1830
+ var RULES = [
1831
+ {
1832
+ id: "HH_PRIVATE_KEY",
1833
+ severity: "high",
1834
+ pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g
1835
+ },
1836
+ {
1837
+ id: "HH_GITHUB_TOKEN",
1838
+ severity: "high",
1839
+ pattern: /\bgh[pousr]_[A-Za-z0-9]{36,255}\b/g
1840
+ },
1841
+ {
1842
+ id: "HH_AWS_ACCESS_KEY",
1843
+ severity: "high",
1844
+ pattern: /\bAKIA[0-9A-Z]{16}\b/g
1845
+ },
1846
+ {
1847
+ id: "HH_AUTHORIZATION_BEARER",
1848
+ severity: "high",
1849
+ pattern: /Authorization\s*:\s*Bearer\s+([A-Za-z0-9._~+/-]{12,})/gi,
1850
+ valueGroup: 1
1851
+ },
1852
+ {
1853
+ id: "HH_DATABASE_URL",
1854
+ severity: "high",
1855
+ pattern: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?):\/\/[^\s"']+/gi
1856
+ },
1857
+ {
1858
+ id: "HH_PASSWORD_VALUE",
1859
+ severity: "medium",
1860
+ pattern: /(?:password|passwd|pwd)\s*[:=]\s*["']?([^\s"'<>]{8,})/gi,
1861
+ valueGroup: 1
1862
+ },
1863
+ {
1864
+ id: "HH_INTERNAL_ADDRESS",
1865
+ severity: "medium",
1866
+ pattern: /\b(?:10\.(?:\d{1,3}\.){2}\d{1,3}|192\.168\.(?:\d{1,3}\.)\d{1,3})\b/g
1867
+ },
1868
+ {
1869
+ id: "HH_WINDOWS_ABSOLUTE_PATH",
1870
+ severity: "low",
1871
+ pattern: /\b[A-Za-z]:\\(?:[^\s<>:"|?*]+\\)*[^\s<>:"|?*]*/g
1872
+ }
1873
+ ];
1874
+ function location(content, offset) {
1875
+ const prefix = content.slice(0, offset);
1876
+ const lines = prefix.split("\n");
1877
+ return { line: lines.length, column: (lines.at(-1)?.length ?? 0) + 1 };
1878
+ }
1879
+ function rawFindings(content) {
1880
+ const findings = [];
1881
+ for (const rule of RULES) {
1882
+ for (const match of content.matchAll(rule.pattern)) {
1883
+ const value = match[rule.valueGroup ?? 0] ?? match[0];
1884
+ const relative2 = match[0].indexOf(value);
1885
+ findings.push({
1886
+ ruleId: rule.id,
1887
+ severity: rule.severity,
1888
+ offset: match.index + Math.max(0, relative2),
1889
+ value
1890
+ });
1891
+ }
1892
+ }
1893
+ for (const candidate of highEntropyCandidates(content)) {
1894
+ findings.push({
1895
+ ruleId: "HH_HIGH_ENTROPY",
1896
+ severity: "high",
1897
+ offset: candidate.offset,
1898
+ value: candidate.value
1899
+ });
1900
+ }
1901
+ return findings;
1902
+ }
1903
+ function scanSensitiveFiles(files, options = {}) {
1904
+ const findings = [];
1905
+ const evidence = [];
1906
+ const recordedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
1907
+ for (const [inputPath, content] of Object.entries(files).sort(([a], [b]) => a.localeCompare(b))) {
1908
+ const path = normalizeManagedPath(inputPath);
1909
+ const ignores = parseInlineIgnores(content);
1910
+ for (const raw of rawFindings(content)) {
1911
+ const position = location(content, raw.offset);
1912
+ const fingerprint = sha256Bytes([
1913
+ SENSITIVE_SCANNER_VERSION,
1914
+ raw.ruleId,
1915
+ path,
1916
+ String(position.line),
1917
+ String(position.column),
1918
+ sha256Bytes(raw.value)
1919
+ ].join("\0"));
1920
+ const overridable = raw.severity !== "high";
1921
+ const explicit = options.overrides?.find((item) => item.finding_fingerprint === fingerprint);
1922
+ const inline = ignores.find((item) => item.ruleId === raw.ruleId);
1923
+ const override = overridable ? explicit ?? (inline === void 0 ? void 0 : {
1924
+ finding_fingerprint: fingerprint,
1925
+ actor: "inline-annotation",
1926
+ reason: inline.reason
1927
+ }) : void 0;
1928
+ if (override !== void 0) {
1929
+ evidence.push(Object.freeze({
1930
+ finding_fingerprint: fingerprint,
1931
+ rule_id: raw.ruleId,
1932
+ path,
1933
+ actor: override.actor,
1934
+ reason: override.reason,
1935
+ source: explicit === void 0 ? "inline-annotation" : "explicit-confirmation",
1936
+ scanner_version: SENSITIVE_SCANNER_VERSION,
1937
+ recorded_at: recordedAt
1938
+ }));
1939
+ }
1940
+ findings.push({
1941
+ rule_id: raw.ruleId,
1942
+ severity: raw.severity,
1943
+ path,
1944
+ line: position.line,
1945
+ column: position.column,
1946
+ fingerprint,
1947
+ redacted_preview: "[REDACTED:" + raw.ruleId + "]",
1948
+ overridable,
1949
+ disposition: override === void 0 ? "blocked" : "overridden"
1950
+ });
1951
+ }
1952
+ }
1953
+ return {
1954
+ scanner_version: SENSITIVE_SCANNER_VERSION,
1955
+ blocked: findings.some((finding) => finding.disposition === "blocked"),
1956
+ findings,
1957
+ override_evidence: Object.freeze(evidence)
1958
+ };
1959
+ }
1960
+
1961
+ // ../core/dist/proposal/preview.js
1962
+ function generateProposalPreview(input, scanOptions = {}) {
1963
+ const diff = buildProposalDiff(input);
1964
+ const includedFiles = Object.fromEntries(Object.values(diff.operations).filter((operation) => operation.operation !== "delete").map((operation) => {
1965
+ const hash = operation.content_sha256;
1966
+ const path = operation.operation === "rename" ? operation.to_path : operation.path;
1967
+ return [path, diff.blobs[hash] ?? ""];
1968
+ }));
1969
+ const security = scanSensitiveFiles(includedFiles, scanOptions);
1970
+ return {
1971
+ blocked: security.blocked,
1972
+ operations: diff.operations,
1973
+ skipped: diff.skipped,
1974
+ blobs: diff.blobs,
1975
+ security
1976
+ };
1977
+ }
1978
+
1979
+ // ../core/dist/push/push.js
1980
+ import { lstat as lstat2, readFile as readFile6, readdir, rm as rm4 } from "node:fs/promises";
1981
+ import { join as join7, relative, resolve as resolve4 } from "node:path";
1982
+ import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
1983
+
1984
+ // ../core/dist/state/baseline.js
1985
+ import { readFile as readFile4 } from "node:fs/promises";
1986
+ import { join as join5 } from "node:path";
1987
+ async function readBaseline(projectRoot) {
1988
+ const content = await readFile4(join5(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
1989
+ return baselineManifestSchema.parse(JSON.parse(content));
1990
+ }
1991
+
1992
+ // ../core/dist/state/locks.js
1993
+ import { randomUUID as randomUUID3 } from "node:crypto";
1994
+ import { readFile as readFile5, rename as rename3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
1995
+ import { join as join6 } from "node:path";
1996
+ async function readLock(path) {
1997
+ return JSON.parse(await readFile5(path, "utf8"));
1998
+ }
1999
+ async function acquireProtocolLock(projectRoot, operation, options = {}) {
2000
+ const layout = await ensureStateLayout(projectRoot);
2001
+ const lockPath = join6(layout.locks, "protocol.lock");
2002
+ const now = options.now ?? Date.now();
2003
+ const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
2004
+ const record = {
2005
+ operation,
2006
+ request_id: options.requestId ?? randomUUID3(),
2007
+ nonce: randomUUID3(),
2008
+ pid: process.pid,
2009
+ started_at_ms: now,
2010
+ heartbeat_at_ms: now
2011
+ };
2012
+ for (let attempt = 0; attempt < 2; attempt += 1) {
2013
+ try {
2014
+ await writeFile3(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
2015
+ return {
2016
+ path: lockPath,
2017
+ operation,
2018
+ async release() {
2019
+ try {
2020
+ const current = await readLock(lockPath);
2021
+ if (current.nonce === record.nonce) {
2022
+ await rm3(lockPath, { force: true });
2023
+ }
2024
+ } catch {
2025
+ }
2026
+ }
2027
+ };
2028
+ } catch (error) {
2029
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") {
2030
+ throw error;
2031
+ }
2032
+ const current = await readLock(lockPath);
2033
+ if (now - current.heartbeat_at_ms <= staleAfterMs) {
2034
+ throw new Error("protocol lock is active for operation " + current.operation, { cause: error });
2035
+ }
2036
+ await rename3(lockPath, lockPath + ".stale-" + randomUUID3());
2037
+ }
2038
+ }
2039
+ throw new Error("unable to acquire protocol lock");
2040
+ }
2041
+
2042
+ // ../core/dist/push/push.js
2043
+ var PushWorkflowError = class extends Error {
2044
+ exitCode;
2045
+ code;
2046
+ constructor(message, exitCode, code) {
2047
+ super(message);
2048
+ this.name = "PushWorkflowError";
2049
+ this.exitCode = exitCode;
2050
+ this.code = code;
2051
+ }
2052
+ };
2053
+ var MANAGED_ROOTS = [
2054
+ ".claude/rules",
2055
+ ".harness/knowledge",
2056
+ ".harness/codebase"
2057
+ ];
2058
+ var MANAGED_FILES = [
2059
+ "CLAUDE.md",
2060
+ "AGENTS.md",
2061
+ ".harness/project.yaml",
2062
+ ".harness/context-index.json"
2063
+ ];
2064
+ var INSTALLED_BUNDLE_PATH2 = ".harness/state/local/installed-harness-bundle.json";
2065
+ async function installedBundlePaths(root) {
2066
+ try {
2067
+ const content = await readFile6(join7(root, INSTALLED_BUNDLE_PATH2), "utf8");
2068
+ const parsed = JSON.parse(content);
2069
+ if (parsed.schema_version === 1 && Array.isArray(parsed.files)) {
2070
+ return new Set(parsed.files);
2071
+ }
2072
+ } catch (error) {
2073
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2074
+ return /* @__PURE__ */ new Set();
2075
+ }
2076
+ throw error;
2077
+ }
2078
+ return /* @__PURE__ */ new Set();
2079
+ }
2080
+ async function exists3(path) {
2081
+ try {
2082
+ await lstat2(path);
2083
+ return true;
2084
+ } catch (error) {
2085
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2086
+ return false;
2087
+ }
2088
+ throw error;
2089
+ }
2090
+ }
2091
+ async function walkFiles(root, current, output) {
2092
+ if (!await exists3(current)) {
2093
+ return;
2094
+ }
2095
+ for (const item of await readdir(current, { withFileTypes: true })) {
2096
+ const path = join7(current, item.name);
2097
+ if (item.isSymbolicLink()) {
2098
+ throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
2099
+ }
2100
+ if (item.isDirectory()) {
2101
+ await walkFiles(root, path, output);
2102
+ } else if (item.isFile()) {
2103
+ output.push(normalizeManagedPath(relative(root, path).replaceAll("\\", "/")));
2104
+ }
2105
+ }
2106
+ }
2107
+ async function managedFiles(projectRoot) {
2108
+ const root = resolve4(projectRoot);
2109
+ const paths = [];
2110
+ for (const path of MANAGED_FILES) {
2111
+ if (await exists3(join7(root, path))) {
2112
+ paths.push(path);
2113
+ }
2114
+ }
2115
+ for (const path of MANAGED_ROOTS) {
2116
+ await walkFiles(root, join7(root, path), paths);
2117
+ }
2118
+ const skillsRoot = join7(root, ".claude", "skills");
2119
+ if (await exists3(skillsRoot)) {
2120
+ for (const item of await readdir(skillsRoot, { withFileTypes: true })) {
2121
+ if (item.isDirectory() && item.name.startsWith("harness-")) {
2122
+ await walkFiles(root, join7(skillsRoot, item.name), paths);
2123
+ }
2124
+ }
2125
+ }
2126
+ const result = {};
2127
+ for (const path of [...new Set(paths)].sort()) {
2128
+ result[path] = await readFile6(join7(root, path), "utf8");
2129
+ }
2130
+ return result;
2131
+ }
2132
+ function proposalBaseline(baseline) {
2133
+ return Object.fromEntries(Object.entries(baseline.files).filter(([, entry]) => entry.baseline_hash !== null && !entry.deleted).map(([path, entry]) => [path, { content_sha256: entry.baseline_hash ?? "" }]));
2134
+ }
2135
+ async function readProject(root) {
2136
+ try {
2137
+ return projectConfigSchema.parse(parseYaml3(await readFile6(join7(root, ".harness", "project.yaml"), "utf8")));
2138
+ } catch {
2139
+ throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
2140
+ }
2141
+ }
2142
+ async function readOptionalJson(path) {
2143
+ try {
2144
+ return JSON.parse(await readFile6(path, "utf8"));
2145
+ } catch (error) {
2146
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2147
+ return null;
2148
+ }
2149
+ throw error;
2150
+ }
2151
+ }
2152
+ async function clientIdFor(root, explicit) {
2153
+ if (explicit !== void 0) {
2154
+ return explicit;
2155
+ }
2156
+ const path = join7(root, ".harness", "state", "local", "client.json");
2157
+ const existing = await readOptionalJson(path);
2158
+ if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
2159
+ return existing.client_id;
2160
+ }
2161
+ const clientId = "cli_" + uuidV7().replaceAll("-", "");
2162
+ await atomicWriteJson(path, {
2163
+ schema_version: 1,
2164
+ client_id: clientId,
2165
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
2166
+ });
2167
+ return clientId;
2168
+ }
2169
+ function newWorkflowState(project, clientId) {
2170
+ return {
2171
+ schema_version: 1,
2172
+ local_project_key: project.project.local_project_key,
2173
+ project_id: project.project.project_id,
2174
+ request_id: uuidV7(),
2175
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
2176
+ client_id: clientId,
2177
+ resolve_idempotency_key: uuidV7(),
2178
+ proposal_manifest_hash: null,
2179
+ session_id: null,
2180
+ session_expires_at: null,
2181
+ max_chunk_bytes: null,
2182
+ session_idempotency_key: uuidV7(),
2183
+ query_idempotency_key: uuidV7(),
2184
+ finalize_idempotency_key: uuidV7(),
2185
+ chunk_idempotency_keys: {}
2186
+ };
2187
+ }
2188
+ function resetSession(state, proposalManifestHash) {
2189
+ return {
2190
+ ...state,
2191
+ proposal_manifest_hash: proposalManifestHash,
2192
+ session_id: null,
2193
+ session_expires_at: null,
2194
+ max_chunk_bytes: null,
2195
+ session_idempotency_key: uuidV7(),
2196
+ query_idempotency_key: uuidV7(),
2197
+ finalize_idempotency_key: uuidV7(),
2198
+ chunk_idempotency_keys: {}
2199
+ };
2200
+ }
2201
+ function makePreview(baseline, files, confirmedProjectLocal, ignorePaths, deletedAt = (/* @__PURE__ */ new Date()).toISOString()) {
2202
+ const filteredFiles = {};
2203
+ for (const [path, content] of Object.entries(files)) {
2204
+ if (!ignorePaths.has(path))
2205
+ filteredFiles[path] = content;
2206
+ }
2207
+ return generateProposalPreview({
2208
+ baseline: proposalBaseline(baseline),
2209
+ files: filteredFiles,
2210
+ deletedAt,
2211
+ deleteReason: "removed from local managed working copy",
2212
+ confirmedProjectLocal
2213
+ });
2214
+ }
2215
+ async function bindProject(root, project, baseline, projectId) {
2216
+ const nextProject = projectConfigSchema.parse({
2217
+ ...project,
2218
+ project: { ...project.project, project_id: projectId }
2219
+ });
2220
+ const nextBaseline = baselineManifestSchema.parse({
2221
+ ...baseline,
2222
+ project_id: projectId
2223
+ });
2224
+ await runTransaction(root, [
2225
+ {
2226
+ operation: "modify",
2227
+ path: ".harness/project.yaml",
2228
+ content: stringifyYaml2(nextProject, { sortMapEntries: true })
2229
+ },
2230
+ {
2231
+ operation: "modify",
2232
+ path: ".harness/state/baseline/manifest.json",
2233
+ content: JSON.stringify(nextBaseline, null, 2) + "\n"
2234
+ }
2235
+ ]);
2236
+ return { project: nextProject, baseline: nextBaseline };
2237
+ }
2238
+ async function pushProject(options) {
2239
+ const root = resolve4(options.projectRoot);
2240
+ let project = await readProject(root);
2241
+ let baseline = await readBaseline(root);
2242
+ const installedPaths = await installedBundlePaths(root);
2243
+ let preview = makePreview(baseline, await managedFiles(root), options.confirmedProjectLocal ?? [], installedPaths);
2244
+ if (preview.blocked) {
2245
+ throw new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED");
2246
+ }
2247
+ if (options.dryRun) {
2248
+ return { preview, proposalId: null, projectId: project.project.project_id };
2249
+ }
2250
+ if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
2251
+ return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
2252
+ }
2253
+ const serverUrl = options.serverUrl ?? project.server.url;
2254
+ if (serverUrl === null || serverUrl === void 0) {
2255
+ throw new PushWorkflowError("server_url is required", 3, "SERVER_URL_REQUIRED");
2256
+ }
2257
+ const tokenEnv = options.tokenEnv ?? project.server.token_env;
2258
+ const token = options.env[tokenEnv];
2259
+ if (token === void 0 || token.trim() === "") {
2260
+ throw new PushWorkflowError("API token environment variable is unset", 8, "TOKEN_INVALID");
2261
+ }
2262
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(tokenEnv)) {
2263
+ throw new PushWorkflowError("token_env is invalid", 3, "TOKEN_ENV_INVALID");
2264
+ }
2265
+ let parsedServerUrl;
2266
+ try {
2267
+ parsedServerUrl = new URL(serverUrl);
2268
+ } catch {
2269
+ throw new PushWorkflowError("server_url is invalid", 3, "SERVER_URL_INVALID");
2270
+ }
2271
+ if (parsedServerUrl.protocol !== "https:") {
2272
+ throw new PushWorkflowError("server_url must use HTTPS", 3, "SERVER_URL_INVALID");
2273
+ }
2274
+ const workflowPath = join7(root, ".harness", "state", "local", "push-workflow.json");
2275
+ const priorWorkflow = await readOptionalJson(workflowPath);
2276
+ const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
2277
+ const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
2278
+ try {
2279
+ const clientId = await clientIdFor(root, options.clientId);
2280
+ let workflow = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow : newWorkflowState(project, clientId);
2281
+ workflow.client_id = clientId;
2282
+ await atomicWriteJson(workflowPath, workflow);
2283
+ preview = makePreview(baseline, await managedFiles(root), options.confirmedProjectLocal ?? [], installedPaths, workflow.created_at);
2284
+ const requestId = workflow.request_id;
2285
+ const client = new HunterHarnessApiClient({
2286
+ serverUrl: parsedServerUrl.toString(),
2287
+ token,
2288
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch }
2289
+ });
2290
+ if (project.project.project_id === null) {
2291
+ const resolved = await client.resolveProject({
2292
+ schema_version: 1,
2293
+ local_project_key: project.project.local_project_key,
2294
+ display_name: project.project.name,
2295
+ requested_project_id: null,
2296
+ client_id: clientId
2297
+ }, requestId, workflow.resolve_idempotency_key);
2298
+ ({ project, baseline } = await bindProject(root, project, baseline, resolved.project_id));
2299
+ workflow.project_id = resolved.project_id;
2300
+ await atomicWriteJson(workflowPath, workflow);
2301
+ preview = makePreview(baseline, await managedFiles(root), options.confirmedProjectLocal ?? [], installedPaths, workflow.created_at);
2302
+ if (preview.blocked) {
2303
+ throw new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED");
2304
+ }
2305
+ }
2306
+ const projectId = project.project.project_id;
2307
+ if (projectId === null) {
2308
+ throw new PushWorkflowError("project binding failed", 4, "PROJECT_BIND_FAILED");
2309
+ }
2310
+ const baseManifestHash = sha256Bytes(canonicalJson(baseline));
2311
+ const proposalManifestHash = sha256Bytes(canonicalJson(preview.operations));
2312
+ if (workflow.proposal_manifest_hash !== proposalManifestHash || workflow.session_expires_at !== null && Date.parse(workflow.session_expires_at) <= Date.now()) {
2313
+ workflow = resetSession(workflow, proposalManifestHash);
2314
+ await atomicWriteJson(workflowPath, workflow);
2315
+ }
2316
+ let session;
2317
+ if (workflow.session_id === null || workflow.max_chunk_bytes === null || workflow.session_expires_at === null) {
2318
+ session = await client.createProposalSession(projectId, {
2319
+ schema_version: 1,
2320
+ request_id: requestId,
2321
+ client_id: clientId,
2322
+ base_project_version: baseline.complete_project_version,
2323
+ base_manifest_hash: baseManifestHash,
2324
+ proposal_manifest: { files: preview.operations },
2325
+ artifact_manifest: { schema_version: 1, files: preview.operations }
2326
+ }, requestId, workflow.session_idempotency_key);
2327
+ workflow.session_id = session.session_id;
2328
+ workflow.session_expires_at = session.expires_at;
2329
+ workflow.max_chunk_bytes = session.max_chunk_bytes;
2330
+ await atomicWriteJson(workflowPath, workflow);
2331
+ } else {
2332
+ session = {
2333
+ session_id: workflow.session_id,
2334
+ expires_at: workflow.session_expires_at,
2335
+ missing_blobs: [],
2336
+ max_chunk_bytes: workflow.max_chunk_bytes,
2337
+ request_id: requestId
2338
+ };
2339
+ }
2340
+ const hashes = Object.keys(preview.blobs).sort();
2341
+ const query = await client.queryBlobs(session.session_id, hashes, requestId, workflow.query_idempotency_key);
2342
+ const missing = /* @__PURE__ */ new Set([...session.missing_blobs, ...query.missing]);
2343
+ for (const hash of missing) {
2344
+ const content = preview.blobs[hash];
2345
+ if (content === void 0) {
2346
+ throw new PushWorkflowError("server requested undeclared blob", 4, "BLOB_NOT_DECLARED");
2347
+ }
2348
+ const bytes = new TextEncoder().encode(content);
2349
+ for (let start = 0; start < bytes.byteLength; start += session.max_chunk_bytes) {
2350
+ const chunkKey = hash + ":" + start;
2351
+ workflow.chunk_idempotency_keys[chunkKey] ??= uuidV7();
2352
+ await atomicWriteJson(workflowPath, workflow);
2353
+ await client.uploadBlobChunk({
2354
+ sessionId: session.session_id,
2355
+ contentSha256: hash,
2356
+ chunk: bytes.slice(start, Math.min(bytes.byteLength, start + session.max_chunk_bytes)),
2357
+ start,
2358
+ total: bytes.byteLength,
2359
+ requestId,
2360
+ idempotencyKey: workflow.chunk_idempotency_keys[chunkKey] ?? uuidV7()
2361
+ });
2362
+ }
2363
+ }
2364
+ const finalized = await client.finalizeProposal(session.session_id, { schema_version: 1, manifest_sha256: proposalManifestHash }, requestId, workflow.finalize_idempotency_key);
2365
+ await atomicWriteJson(join7(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
2366
+ schema_version: 1,
2367
+ request_id: requestId,
2368
+ project_id: projectId,
2369
+ proposal_id: finalized.proposal_id,
2370
+ status: finalized.status,
2371
+ operation_count: preview.operations.length,
2372
+ recorded_at: (/* @__PURE__ */ new Date()).toISOString()
2373
+ });
2374
+ await rm4(workflowPath, { force: true });
2375
+ return { preview, proposalId: finalized.proposal_id, projectId };
2376
+ } catch (error) {
2377
+ if (error instanceof PushWorkflowError) {
2378
+ throw error;
2379
+ }
2380
+ if (error instanceof ApiError) {
2381
+ const exitCode = error.status === 401 ? 8 : error.status === 409 ? 5 : 4;
2382
+ throw new PushWorkflowError(error.message, exitCode, error.code);
2383
+ }
2384
+ throw new PushWorkflowError(error instanceof Error ? error.message : "push failed", 4, "NETWORK_OR_SERVER_ERROR");
2385
+ } finally {
2386
+ await lock.release();
2387
+ }
2388
+ }
2389
+
2390
+ // ../core/dist/skill/frontmatter.js
2391
+ import { parse as parseYaml4 } from "yaml";
2392
+ import { z as z12 } from "zod";
2393
+
2394
+ // ../core/dist/skill/fixer.js
2395
+ import { parse as parseYaml5, stringify as stringifyYaml3 } from "yaml";
2396
+
2397
+ // ../core/dist/skill/agents.js
2398
+ var AGENT_DESCRIPTORS = {
2399
+ "claude-code": {
2400
+ agent: "claude-code",
2401
+ installable: true,
2402
+ installTarget: (slug) => `.claude/skills/${slug}/`,
2403
+ installMode: "folder"
2404
+ },
2405
+ codex: {
2406
+ agent: "codex",
2407
+ installable: true,
2408
+ installTarget: () => "AGENTS.md",
2409
+ installMode: "managed_block",
2410
+ blockId: (slug) => `harness-skill-${slug}`
2411
+ },
2412
+ cursor: {
2413
+ agent: "cursor",
2414
+ installable: true,
2415
+ installTarget: (slug) => `.cursor/rules/${slug}.mdc`,
2416
+ installMode: "file"
2417
+ },
2418
+ generic: {
2419
+ agent: "generic",
2420
+ installable: true,
2421
+ installTarget: (slug) => `.agent-skills/${slug}.md`,
2422
+ installMode: "file"
2423
+ },
2424
+ mcp: {
2425
+ agent: "mcp",
2426
+ installable: false,
2427
+ installTarget: () => "",
2428
+ installMode: "file"
2429
+ }
2430
+ };
2431
+ var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
2432
+
2433
+ // ../core/dist/transaction/recovery.js
2434
+ import { readFile as readFile7, readdir as readdir2, rm as rm5, stat as stat3 } from "node:fs/promises";
2435
+ import { join as join8 } from "node:path";
2436
+ async function recoverTransaction(projectRoot, transactionId) {
2437
+ const journal = JSON.parse(await readFile7(join8(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
2438
+ if (journal.state === "committed") {
2439
+ return { transactionId, status: "committed" };
2440
+ }
2441
+ return rollbackTransaction(projectRoot, transactionId);
2442
+ }
2443
+ var RECOVERY_STATES = /* @__PURE__ */ new Set([
2444
+ "prepared",
2445
+ "applying",
2446
+ "interrupted",
2447
+ "rolling_back",
2448
+ "recovery_required"
2449
+ ]);
2450
+ async function listTransactions(projectRoot) {
2451
+ const root = stateLayout(projectRoot).transactions;
2452
+ let names;
2453
+ try {
2454
+ names = await readdir2(root);
2455
+ } catch (error) {
2456
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2457
+ return [];
2458
+ }
2459
+ throw error;
2460
+ }
2461
+ const transactions = [];
2462
+ for (const transactionId of names) {
2463
+ try {
2464
+ const journal = JSON.parse(await readFile7(join8(root, transactionId, "journal.json"), "utf8"));
2465
+ transactions.push({
2466
+ transactionId,
2467
+ kind: journal.kind,
2468
+ state: journal.state,
2469
+ createdAt: journal.created_at
2470
+ });
2471
+ } catch {
2472
+ }
2473
+ }
2474
+ return transactions.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
2475
+ }
2476
+ async function pendingTransactions(projectRoot) {
2477
+ return (await listTransactions(projectRoot)).filter((item) => RECOVERY_STATES.has(item.state));
2478
+ }
2479
+ async function pathExists(path) {
2480
+ try {
2481
+ await stat3(path);
2482
+ return true;
2483
+ } catch (error) {
2484
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2485
+ return false;
2486
+ }
2487
+ throw error;
2488
+ }
2489
+ }
2490
+ async function rollbackLatestCommittedUpdate(projectRoot) {
2491
+ const latest = (await listTransactions(projectRoot)).find((item) => item.kind === "update" && item.state === "committed");
2492
+ if (latest === void 0) {
2493
+ throw new Error("no committed update transaction is available for rollback");
2494
+ }
2495
+ const transactionRoot = join8(stateLayout(projectRoot).transactions, latest.transactionId);
2496
+ const journal = JSON.parse(await readFile7(join8(transactionRoot, "journal.json"), "utf8"));
2497
+ const after = JSON.parse(await readFile7(join8(transactionRoot, "after", "manifest.json"), "utf8"));
2498
+ for (const entry of after) {
2499
+ const target = join8(projectRoot, entry.path);
2500
+ const exists5 = await pathExists(target);
2501
+ if (exists5 !== entry.exists || exists5 && await sha256File(target) !== entry.hash) {
2502
+ throw new Error("cannot rollback dirty path: " + entry.path);
2503
+ }
2504
+ }
2505
+ const operations = [];
2506
+ const seen = /* @__PURE__ */ new Set();
2507
+ for (const snapshot of journal.snapshots) {
2508
+ if (seen.has(snapshot.path)) {
2509
+ continue;
2510
+ }
2511
+ seen.add(snapshot.path);
2512
+ const target = join8(projectRoot, snapshot.path);
2513
+ const exists5 = await pathExists(target);
2514
+ if (snapshot.existed && snapshot.snapshot_name !== null) {
2515
+ const content = await readFile7(join8(transactionRoot, "before", snapshot.snapshot_name));
2516
+ operations.push({
2517
+ operation: exists5 ? "modify" : "add",
2518
+ path: snapshot.path,
2519
+ content
2520
+ });
2521
+ } else if (exists5) {
2522
+ operations.push({ operation: "delete", path: snapshot.path });
2523
+ }
2524
+ }
2525
+ return runTransaction(projectRoot, operations, {
2526
+ id: "tx_rollback_" + Date.now() + "_" + uuidV7(),
2527
+ kind: "rollback"
2528
+ });
2529
+ }
2530
+ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Date()) {
2531
+ const transactions = await listTransactions(projectRoot);
2532
+ const committedUpdates = transactions.filter((item) => item.kind === "update" && item.state === "committed");
2533
+ const keepCommitted = new Set(committedUpdates.slice(0, 10).map((item) => item.transactionId));
2534
+ const removed = [];
2535
+ for (const item of transactions) {
2536
+ if (RECOVERY_STATES.has(item.state) || keepCommitted.has(item.transactionId)) {
2537
+ continue;
2538
+ }
2539
+ const ageDays = (now.getTime() - Date.parse(item.createdAt)) / 864e5;
2540
+ const removable = item.state === "rolled_back" ? ageDays > 7 : item.state === "committed" && ageDays > 30;
2541
+ if (!removable) {
2542
+ continue;
2543
+ }
2544
+ await rm5(join8(stateLayout(projectRoot).transactions, item.transactionId), {
2545
+ recursive: true,
2546
+ force: true
2547
+ });
2548
+ removed.push(item.transactionId);
2549
+ }
2550
+ return removed;
2551
+ }
2552
+
2553
+ // ../core/dist/update/conflicts.js
2554
+ function operationTargetPath(operation) {
2555
+ return operation.operation === "rename" ? operation.to_path : operation.path;
2556
+ }
2557
+ function operationSourcePath(operation) {
2558
+ return operation.operation === "rename" ? operation.from_path : operation.path;
2559
+ }
2560
+ function operationAlreadyApplied(operation, baseline, projectVersion) {
2561
+ const target = baseline.files[operationTargetPath(operation)];
2562
+ if (operation.operation === "delete") {
2563
+ return target?.deleted === true && target.last_applied_version === projectVersion;
2564
+ }
2565
+ return target?.deleted === false && target?.last_applied_version === projectVersion && target.baseline_hash === operation.content_sha256;
2566
+ }
2567
+ function managedBlockDirty(currentContent, managedBlockHash) {
2568
+ if (managedBlockHash === void 0) {
2569
+ return true;
2570
+ }
2571
+ const block = extractManagedBlock(currentContent);
2572
+ return block === null || sha256Bytes(block) !== managedBlockHash;
2573
+ }
2574
+
2575
+ // ../core/dist/update/update.js
2576
+ import { lstat as lstat3, readFile as readFile8, rm as rm6 } from "node:fs/promises";
2577
+ import { join as join9, resolve as resolve5 } from "node:path";
2578
+ import { parse as parseYaml6 } from "yaml";
2579
+ var UpdateWorkflowError = class extends Error {
2580
+ exitCode;
2581
+ code;
2582
+ constructor(message, exitCode, code) {
2583
+ super(message);
2584
+ this.name = "UpdateWorkflowError";
2585
+ this.exitCode = exitCode;
2586
+ this.code = code;
2587
+ }
2588
+ };
2589
+ async function pathExists2(path) {
2590
+ try {
2591
+ await lstat3(path);
2592
+ return true;
2593
+ } catch (error) {
2594
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2595
+ return false;
2596
+ }
2597
+ throw error;
2598
+ }
2599
+ }
2600
+ async function optionalContent(path) {
2601
+ return await pathExists2(path) ? readFile8(path, "utf8") : null;
2602
+ }
2603
+ function manifestPayloadHash(manifest) {
2604
+ const payload = { ...manifest };
2605
+ delete payload.manifest_sha256;
2606
+ return sha256Bytes(canonicalJson(payload));
2607
+ }
2608
+ function expectedBase(operation) {
2609
+ return operation.operation === "add" ? null : operation.base_content_sha256;
2610
+ }
2611
+ function contentHash(operation) {
2612
+ return operation.operation === "delete" ? null : operation.content_sha256;
2613
+ }
2614
+ async function loadBlob(root, client, artifactId, operation, requestId, dryRun) {
2615
+ if (operation.operation === "delete") {
2616
+ return null;
2617
+ }
2618
+ const hash = operation.content_sha256;
2619
+ const cacheRoot = join9(root, ".harness", "cache", "server-artifacts", artifactId);
2620
+ const cachePath = join9(cacheRoot, "blobs", hash.replace(":", "_"));
2621
+ if (await pathExists2(cachePath) && await sha256File(cachePath) === hash) {
2622
+ return readFile8(cachePath, "utf8");
2623
+ }
2624
+ const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
2625
+ if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
2626
+ await rm6(cachePath, { force: true });
2627
+ throw new UpdateWorkflowError("artifact blob size or hash mismatch", 4, "ARTIFACT_HASH_MISMATCH");
2628
+ }
2629
+ if (!dryRun) {
2630
+ await atomicWriteFile(cachePath, bytes);
2631
+ }
2632
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
2633
+ }
2634
+ function nextBaselineEntry(operation, finalContent, projectVersion) {
2635
+ const block = finalContent === null ? null : extractManagedBlock(finalContent);
2636
+ return {
2637
+ baseline_hash: contentHash(operation),
2638
+ local_hash_at_apply: finalContent === null ? null : sha256Bytes(finalContent),
2639
+ file_kind: operation.file_kind,
2640
+ last_applied_version: projectVersion,
2641
+ deleted: operation.operation === "delete",
2642
+ ...block === null ? {} : { managed_block_hash: sha256Bytes(block) }
2643
+ };
2644
+ }
2645
+ function transactionOperation(item) {
2646
+ if (item.equivalent) {
2647
+ return null;
2648
+ }
2649
+ const operation = item.operation;
2650
+ const target = operationTargetPath(operation);
2651
+ if (operation.operation === "delete") {
2652
+ return item.finalContent === null ? { operation: "delete", path: target } : { operation: "modify", path: target, content: item.finalContent };
2653
+ }
2654
+ if (operation.operation === "rename") {
2655
+ return {
2656
+ operation: "rename",
2657
+ from_path: operation.from_path,
2658
+ to_path: operation.to_path,
2659
+ content: item.finalContent ?? item.content ?? ""
2660
+ };
2661
+ }
2662
+ return {
2663
+ operation: "modify",
2664
+ path: target,
2665
+ content: item.finalContent ?? item.content ?? ""
2666
+ };
2667
+ }
2668
+ async function updateProject(options) {
2669
+ const root = resolve5(options.projectRoot);
2670
+ let project;
2671
+ try {
2672
+ project = projectConfigSchema.parse(parseYaml6(await readFile8(join9(root, ".harness", "project.yaml"), "utf8")));
2673
+ } catch {
2674
+ throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
2675
+ }
2676
+ if (project.project.project_id === null) {
2677
+ throw new UpdateWorkflowError("project is not bound to a server", 3, "PROJECT_NOT_BOUND");
2678
+ }
2679
+ const serverUrl = options.serverUrl ?? project.server.url;
2680
+ const tokenEnv = options.tokenEnv ?? project.server.token_env;
2681
+ if (serverUrl === null || serverUrl === void 0) {
2682
+ throw new UpdateWorkflowError("server_url is required", 3, "SERVER_URL_REQUIRED");
2683
+ }
2684
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(tokenEnv)) {
2685
+ throw new UpdateWorkflowError("token_env is invalid", 3, "TOKEN_ENV_INVALID");
2686
+ }
2687
+ const token = options.env[tokenEnv];
2688
+ if (token === void 0 || token.trim() === "") {
2689
+ throw new UpdateWorkflowError("API token environment variable is unset", 8, "TOKEN_INVALID");
2690
+ }
2691
+ const requestId = uuidV7();
2692
+ const baseline = await readBaseline(root);
2693
+ let parsedServerUrl;
2694
+ try {
2695
+ parsedServerUrl = new URL(serverUrl);
2696
+ } catch {
2697
+ throw new UpdateWorkflowError("server_url is invalid", 3, "SERVER_URL_INVALID");
2698
+ }
2699
+ if (parsedServerUrl.protocol !== "https:") {
2700
+ throw new UpdateWorkflowError("server_url must use HTTPS", 3, "SERVER_URL_INVALID");
2701
+ }
2702
+ const client = new HunterHarnessApiClient({
2703
+ serverUrl: parsedServerUrl.toString(),
2704
+ token,
2705
+ ...options.fetch === void 0 ? {} : { fetch: options.fetch }
2706
+ });
2707
+ try {
2708
+ const discovery = await client.getUpdateManifest(project.project.project_id, {
2709
+ base_project_version: baseline.complete_project_version,
2710
+ base_manifest_hash: sha256Bytes(canonicalJson(baseline)),
2711
+ adapter: project.adapters.enabled[0] ?? "claude-code",
2712
+ profile: project.project.profiles[0] ?? "general"
2713
+ }, requestId);
2714
+ if (!discovery.delta_available || discovery.artifact_id === null) {
2715
+ return {
2716
+ requestId,
2717
+ projectId: project.project.project_id,
2718
+ artifactId: null,
2719
+ observedProjectVersion: discovery.observed_project_version,
2720
+ operations: [],
2721
+ applied: [],
2722
+ skipped: [],
2723
+ transactionId: null,
2724
+ dryRun: options.dryRun
2725
+ };
2726
+ }
2727
+ const manifest = artifactManifestSchema.parse(await client.getArtifactManifest(discovery.artifact_id, requestId));
2728
+ if (manifest.artifact_id !== discovery.artifact_id || manifest.project_id !== project.project.project_id || manifest.project_version === null || manifestPayloadHash(manifest) !== manifest.manifest_sha256) {
2729
+ throw new UpdateWorkflowError("artifact manifest integrity check failed", 4, "ARTIFACT_HASH_MISMATCH");
2730
+ }
2731
+ const blobs = /* @__PURE__ */ new Map();
2732
+ for (const operation of manifest.files) {
2733
+ const policy = classifyFile(operationTargetPath(operation));
2734
+ if (decideUpdate(policy, false).apply) {
2735
+ blobs.set(operation, await loadBlob(root, client, manifest.artifact_id, operation, requestId, options.dryRun));
2736
+ }
2737
+ }
2738
+ const prepared = [];
2739
+ const skipped = [];
2740
+ for (const operation of manifest.files) {
2741
+ if (operationAlreadyApplied(operation, baseline, manifest.project_version)) {
2742
+ continue;
2743
+ }
2744
+ const source = operationSourcePath(operation);
2745
+ const target = operationTargetPath(operation);
2746
+ const policy = classifyFile(target);
2747
+ const staticDecision = decideUpdate(policy, false);
2748
+ if (!staticDecision.apply) {
2749
+ skipped.push({ path: target, operation: operation.operation, reason: staticDecision.reason });
2750
+ continue;
2751
+ }
2752
+ const previous = baseline.files[source];
2753
+ if (expectedBase(operation) !== (previous?.baseline_hash ?? null)) {
2754
+ skipped.push({ path: target, operation: operation.operation, reason: "baseline-diverged" });
2755
+ continue;
2756
+ }
2757
+ const sourceContent = await optionalContent(join9(root, source));
2758
+ const targetContent = target === source ? sourceContent : await optionalContent(join9(root, target));
2759
+ const incoming = blobs.get(operation) ?? null;
2760
+ const incomingHash = contentHash(operation);
2761
+ let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
2762
+ let dirty = false;
2763
+ if (operation.operation === "add") {
2764
+ if (targetContent !== null && !equivalent) {
2765
+ if (policy.update_policy === "managed-block-only" && incoming !== null) {
2766
+ const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
2767
+ equivalent = extractManagedBlock(targetContent) === incomingBlock;
2768
+ dirty = !equivalent;
2769
+ } else {
2770
+ dirty = true;
2771
+ }
2772
+ }
2773
+ } else if (sourceContent === null) {
2774
+ dirty = operation.operation !== "delete";
2775
+ } else if (policy.update_policy === "managed-block-only") {
2776
+ dirty = previous?.managed_block_hash === void 0 ? (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent) : managedBlockDirty(sourceContent, previous.managed_block_hash);
2777
+ } else {
2778
+ dirty = (previous?.local_hash_at_apply ?? previous?.baseline_hash) !== sha256Bytes(sourceContent);
2779
+ }
2780
+ if (operation.operation === "rename" && targetContent !== null && !equivalent) {
2781
+ dirty = true;
2782
+ }
2783
+ if (dirty) {
2784
+ skipped.push({ path: target, operation: operation.operation, reason: "local-dirty" });
2785
+ continue;
2786
+ }
2787
+ let finalContent = incoming;
2788
+ if (policy.update_policy === "managed-block-only") {
2789
+ if (operation.operation === "delete") {
2790
+ finalContent = sourceContent === null ? null : removeManagedBlock(sourceContent);
2791
+ equivalent = finalContent === sourceContent;
2792
+ } else if (incoming !== null) {
2793
+ const incomingBlock = extractManagedBlock(incoming) ?? incoming.trim();
2794
+ const blockId = operation.operation === "add" || operation.operation === "modify" ? operation.block_id : void 0;
2795
+ finalContent = blockId !== void 0 ? upsertManagedBlockById(targetContent ?? "", blockId, incomingBlock) : upsertManagedBlock(targetContent ?? "", incomingBlock);
2796
+ equivalent = finalContent === targetContent;
2797
+ }
2798
+ } else if (operation.operation === "delete" && sourceContent === null) {
2799
+ equivalent = true;
2800
+ }
2801
+ prepared.push({ operation, content: incoming, finalContent, equivalent });
2802
+ }
2803
+ const applied = prepared.map((item) => operationTargetPath(item.operation));
2804
+ if (options.dryRun) {
2805
+ return {
2806
+ requestId,
2807
+ projectId: project.project.project_id,
2808
+ artifactId: manifest.artifact_id,
2809
+ observedProjectVersion: manifest.project_version,
2810
+ operations: manifest.files,
2811
+ applied,
2812
+ skipped,
2813
+ transactionId: null,
2814
+ dryRun: true
2815
+ };
2816
+ }
2817
+ await atomicWriteJson(join9(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
2818
+ const lock = await acquireProtocolLock(root, "update", { requestId });
2819
+ try {
2820
+ const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
2821
+ for (const item of prepared) {
2822
+ const target = operationTargetPath(item.operation);
2823
+ nextBaseline.files[target] = nextBaselineEntry(item.operation, item.finalContent, manifest.project_version);
2824
+ if (item.operation.operation === "rename") {
2825
+ nextBaseline.files[item.operation.from_path] = {
2826
+ baseline_hash: null,
2827
+ local_hash_at_apply: null,
2828
+ file_kind: item.operation.file_kind,
2829
+ last_applied_version: manifest.project_version,
2830
+ deleted: true
2831
+ };
2832
+ }
2833
+ }
2834
+ if (skipped.length === 0) {
2835
+ nextBaseline.complete_project_version = manifest.project_version;
2836
+ nextBaseline.artifact_manifest_hash = manifest.manifest_sha256;
2837
+ }
2838
+ const transactionId = "tx_update_" + Date.now() + "_" + uuidV7();
2839
+ const reportPath = ".harness/reports/update-" + requestId + ".json";
2840
+ const report = {
2841
+ schema_version: 1,
2842
+ request_id: requestId,
2843
+ artifact_id: manifest.artifact_id,
2844
+ observed_project_version: manifest.project_version,
2845
+ status: skipped.length === 0 ? "applied" : "partial_due_to_dirty",
2846
+ applied,
2847
+ skipped,
2848
+ transaction_id: transactionId
2849
+ };
2850
+ const operations = prepared.map((item) => transactionOperation(item)).filter((item) => item !== null);
2851
+ operations.push({
2852
+ operation: "modify",
2853
+ path: ".harness/state/baseline/manifest.json",
2854
+ content: JSON.stringify(nextBaseline, null, 2) + "\n"
2855
+ }, {
2856
+ operation: "add",
2857
+ path: reportPath,
2858
+ content: JSON.stringify(report, null, 2) + "\n"
2859
+ }, {
2860
+ operation: "modify",
2861
+ path: ".harness/state/local/last-update.json",
2862
+ content: JSON.stringify(report, null, 2) + "\n"
2863
+ });
2864
+ await runTransaction(root, operations, {
2865
+ id: transactionId,
2866
+ kind: "update",
2867
+ ...options.transactionOptions ?? {}
2868
+ });
2869
+ return {
2870
+ requestId,
2871
+ projectId: project.project.project_id,
2872
+ artifactId: manifest.artifact_id,
2873
+ observedProjectVersion: manifest.project_version,
2874
+ operations: manifest.files,
2875
+ applied,
2876
+ skipped,
2877
+ transactionId,
2878
+ dryRun: false
2879
+ };
2880
+ } finally {
2881
+ await lock.release();
2882
+ }
2883
+ } catch (error) {
2884
+ if (error instanceof UpdateWorkflowError) {
2885
+ throw error;
2886
+ }
2887
+ if (error instanceof ApiError) {
2888
+ throw new UpdateWorkflowError(error.message, error.status === 401 || error.status === 403 ? 8 : error.status === 409 ? 5 : 4, error.code);
2889
+ }
2890
+ if (error instanceof Error && error.name === "ZodError") {
2891
+ throw new UpdateWorkflowError("artifact schema validation failed", 7, "SCHEMA_VALIDATION_FAILED");
2892
+ }
2893
+ throw new UpdateWorkflowError(error instanceof Error ? error.message : "update failed", 4, "NETWORK_OR_SERVER_ERROR");
2894
+ }
2895
+ }
2896
+
2897
+ // src/config/init-config.ts
2898
+ import { isAbsolute as isAbsolute2, join as join10 } from "node:path";
2899
+ import { readFile as readFile9 } from "node:fs/promises";
2900
+ var InitConfigurationError = class extends Error {
2901
+ exitCode;
2902
+ constructor(message, exitCode = 3, options) {
2903
+ super(message, options);
2904
+ this.name = "InitConfigurationError";
2905
+ this.exitCode = exitCode;
2906
+ }
2907
+ };
2908
+ function normalizeProfile(value) {
2909
+ if (value === void 0) return void 0;
2910
+ if (value === "" || value === "1" || value === "general") return "general";
2911
+ if (value === "2" || value === "java") return "java";
2912
+ throw new InitConfigurationError("profile must be general or java");
2913
+ }
2914
+ async function resolveInitConfig(cwd, flags, promptMissing) {
2915
+ let fileConfig = {};
2916
+ if (flags.config !== void 0) {
2917
+ const path = isAbsolute2(flags.config) ? flags.config : join10(cwd, flags.config);
2918
+ try {
2919
+ fileConfig = JSON.parse(await readFile9(path, "utf8"));
2920
+ } catch (error) {
2921
+ throw new InitConfigurationError(
2922
+ "unable to read init config: " + (error instanceof Error ? error.message : String(error)),
2923
+ 3,
2924
+ { cause: error }
2925
+ );
2926
+ }
2927
+ }
2928
+ const configuredAdapter = fileConfig.adapter ?? flags.adapter ?? "claude-code";
2929
+ if (configuredAdapter !== "claude-code") {
2930
+ throw new InitConfigurationError("only claude-code is supported");
2931
+ }
2932
+ const profile = normalizeProfile(
2933
+ fileConfig.profile ?? flags.profile ?? (promptMissing === void 0 ? void 0 : await promptMissing())
2934
+ );
2935
+ const candidate = {
2936
+ adapter: "claude-code",
2937
+ profile,
2938
+ server_url: fileConfig.server_url ?? flags.serverUrl ?? null,
2939
+ token_env: fileConfig.token_env ?? flags.tokenEnv ?? "HUNTER_HARNESS_TOKEN",
2940
+ project_id: fileConfig.project_id ?? null,
2941
+ features: fileConfig.features
2942
+ };
2943
+ if (candidate.profile === void 0) {
2944
+ throw new InitConfigurationError("profile is required");
2945
+ }
2946
+ const parsed = initConfigSchema.safeParse(candidate);
2947
+ if (!parsed.success) {
2948
+ throw new InitConfigurationError(
2949
+ "init config schema validation failed: " + parsed.error.message,
2950
+ 7
2951
+ );
2952
+ }
2953
+ return parsed.data;
2954
+ }
2955
+
2956
+ // src/output/json.ts
2957
+ function serializeCliResult(result) {
2958
+ return JSON.stringify(result) + "\n";
2959
+ }
2960
+
2961
+ // src/commands/configure.ts
2962
+ async function runConfigure(options, dependencies) {
2963
+ const requestId = uuidV7();
2964
+ try {
2965
+ const config = await resolveInitConfig(
2966
+ dependencies.cwd,
2967
+ options,
2968
+ options.nonInteractive === true ? void 0 : () => dependencies.prompt(
2969
+ "\u8BF7\u9009\u62E9 Harness \u7C7B\u578B\uFF1A\n1. \u901A\u7528\uFF08\u9ED8\u8BA4\uFF09\n2. Java\n\u8BF7\u8F93\u5165 1 \u6216 2 [1]: "
2970
+ ).then((answer) => answer.trim())
2971
+ );
2972
+ if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
2973
+ dependencies.stderr("non-interactive writes require --yes\n");
2974
+ return 2;
2975
+ }
2976
+ const result = await initializeProject({
2977
+ projectRoot: dependencies.cwd,
2978
+ resourcesRoot: dependencies.resourcesRoot,
2979
+ config,
2980
+ dryRun: options.dryRun === true
2981
+ });
2982
+ const output = {
2983
+ schema_version: 1,
2984
+ command: "configure",
2985
+ request_id: requestId,
2986
+ dry_run: options.dryRun === true,
2987
+ ok: true,
2988
+ exit_code: 0,
2989
+ project_id: result.projectConfig.project.project_id,
2990
+ summary: { planned: result.paths.length, applied: options.dryRun === true ? 0 : result.paths.length },
2991
+ items: result.paths.map((path) => ({ path, status: options.dryRun === true ? "planned" : "applied" })),
2992
+ warnings: [],
2993
+ errors: []
2994
+ };
2995
+ dependencies.stdout(options.json === true ? serializeCliResult(output) : "Hunter Harness initialized " + result.paths.length + " files.\n");
2996
+ return 0;
2997
+ } catch (error) {
2998
+ const exitCode = error instanceof InitConfigurationError ? error.exitCode : 1;
2999
+ const message = error instanceof Error ? error.message : String(error);
3000
+ dependencies.stderr(message + "\n");
3001
+ if (options.json === true) {
3002
+ dependencies.stdout(serializeCliResult({
3003
+ schema_version: 1,
3004
+ command: "configure",
3005
+ request_id: requestId,
3006
+ dry_run: options.dryRun === true,
3007
+ ok: false,
3008
+ exit_code: exitCode,
3009
+ project_id: null,
3010
+ summary: { planned: 0, applied: 0 },
3011
+ items: [],
3012
+ warnings: [],
3013
+ errors: [{ message }]
3014
+ }));
3015
+ }
3016
+ return exitCode;
3017
+ }
3018
+ }
3019
+
3020
+ // src/commands/push.ts
3021
+ async function runPush(options, dependencies) {
3022
+ const requestId = uuidV7();
3023
+ if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
3024
+ dependencies.stderr("non-interactive push requires --yes\n");
3025
+ return 2;
3026
+ }
3027
+ try {
3028
+ const result = await pushProject({
3029
+ projectRoot: dependencies.cwd,
3030
+ ...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
3031
+ ...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
3032
+ env: dependencies.env,
3033
+ dryRun: options.dryRun === true,
3034
+ fetch: dependencies.fetch,
3035
+ ...options.yes === true || options.nonInteractive === true ? {} : { confirmProposal: async () => {
3036
+ const answer = await dependencies.prompt("Create this proposal? [y/N]: ");
3037
+ return /^(?:y|yes)$/i.test(answer.trim());
3038
+ } }
3039
+ });
3040
+ if ("cancelled" in result && result.cancelled === true) {
3041
+ return 2;
3042
+ }
3043
+ const items = result.preview.operations.map((operation) => ({
3044
+ path: operation.operation === "rename" ? operation.to_path : operation.path,
3045
+ operation: operation.operation,
3046
+ file_kind: operation.file_kind,
3047
+ status: options.dryRun === true ? "planned" : "submitted",
3048
+ reason: null,
3049
+ size_bytes: "size_bytes" in operation ? operation.size_bytes : 0,
3050
+ content_sha256: "content_sha256" in operation ? operation.content_sha256 : operation.tombstone.previous_sha256
3051
+ }));
3052
+ const output = {
3053
+ schema_version: 1,
3054
+ command: "push",
3055
+ request_id: requestId,
3056
+ dry_run: options.dryRun === true,
3057
+ ok: true,
3058
+ exit_code: 0,
3059
+ project_id: result.projectId,
3060
+ summary: {
3061
+ planned: result.preview.operations.length,
3062
+ submitted: options.dryRun === true ? 0 : result.preview.operations.length,
3063
+ skipped: result.preview.skipped.length,
3064
+ findings: result.preview.security.findings.length
3065
+ },
3066
+ items,
3067
+ warnings: result.preview.skipped,
3068
+ errors: []
3069
+ };
3070
+ dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Proposal " + result.proposalId + " created.\n");
3071
+ return 0;
3072
+ } catch (error) {
3073
+ const exitCode = error instanceof PushWorkflowError ? error.exitCode : 1;
3074
+ const message = error instanceof Error ? error.message : String(error);
3075
+ dependencies.stderr(message + "\n");
3076
+ if (options.json === true) {
3077
+ dependencies.stdout(serializeCliResult({
3078
+ schema_version: 1,
3079
+ command: "push",
3080
+ request_id: requestId,
3081
+ dry_run: options.dryRun === true,
3082
+ ok: false,
3083
+ exit_code: exitCode,
3084
+ project_id: null,
3085
+ summary: { planned: 0, submitted: 0 },
3086
+ items: [],
3087
+ warnings: [],
3088
+ errors: [{
3089
+ code: error instanceof PushWorkflowError ? error.code : "GENERAL_FAILURE",
3090
+ message
3091
+ }]
3092
+ }));
3093
+ }
3094
+ return exitCode;
3095
+ }
3096
+ }
3097
+
3098
+ // src/commands/update.ts
3099
+ async function runUpdate(options, dependencies) {
3100
+ const requestId = uuidV7();
3101
+ if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
3102
+ dependencies.stderr("non-interactive update requires --yes\n");
3103
+ return 2;
3104
+ }
3105
+ const execute = async (dryRun) => updateProject({
3106
+ projectRoot: dependencies.cwd,
3107
+ ...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
3108
+ ...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
3109
+ env: dependencies.env,
3110
+ dryRun,
3111
+ fetch: dependencies.fetch
3112
+ });
3113
+ try {
3114
+ let result;
3115
+ if (options.dryRun !== true && options.yes !== true && options.nonInteractive !== true) {
3116
+ const preview = await execute(true);
3117
+ const answer = await dependencies.prompt(
3118
+ "Apply " + preview.applied.length + " eligible update items? [y/N]: "
3119
+ );
3120
+ if (!/^(?:y|yes)$/i.test(answer.trim())) {
3121
+ return 2;
3122
+ }
3123
+ result = await execute(false);
3124
+ } else {
3125
+ result = await execute(options.dryRun === true);
3126
+ }
3127
+ const applied = new Set(result.applied);
3128
+ const skippedByPath = new Map(result.skipped.map((item) => [item.path, item]));
3129
+ const items = result.operations.map((operation) => {
3130
+ const path = operation.operation === "rename" ? operation.to_path : operation.path;
3131
+ const skipped = skippedByPath.get(path);
3132
+ return {
3133
+ path,
3134
+ operation: operation.operation,
3135
+ file_kind: operation.file_kind,
3136
+ policy: "update",
3137
+ status: skipped !== void 0 ? "skipped" : applied.has(path) ? options.dryRun === true ? "planned" : "applied" : "already-applied",
3138
+ reason: skipped?.reason ?? null,
3139
+ size_bytes: "size_bytes" in operation ? operation.size_bytes : 0
3140
+ };
3141
+ });
3142
+ const exitCode = result.skipped.length > 0 ? 5 : 0;
3143
+ const output = {
3144
+ schema_version: 1,
3145
+ command: "update",
3146
+ request_id: requestId,
3147
+ dry_run: options.dryRun === true,
3148
+ ok: exitCode === 0,
3149
+ exit_code: exitCode,
3150
+ project_id: result.projectId,
3151
+ summary: {
3152
+ discovered: result.operations.length,
3153
+ applied: options.dryRun === true ? 0 : result.applied.length,
3154
+ planned: options.dryRun === true ? result.applied.length : 0,
3155
+ skipped: result.skipped.length
3156
+ },
3157
+ items,
3158
+ warnings: result.skipped,
3159
+ errors: []
3160
+ };
3161
+ dependencies.stdout(options.json === true ? serializeCliResult(output) : result.artifactId === null ? "No approved updates are available.\n" : "Update applied " + result.applied.length + " items; skipped " + result.skipped.length + ".\n");
3162
+ return exitCode;
3163
+ } catch (error) {
3164
+ const exitCode = error instanceof UpdateWorkflowError ? error.exitCode : 1;
3165
+ const message = error instanceof Error ? error.message : String(error);
3166
+ dependencies.stderr(message + "\n");
3167
+ if (options.json === true) {
3168
+ dependencies.stdout(serializeCliResult({
3169
+ schema_version: 1,
3170
+ command: "update",
3171
+ request_id: requestId,
3172
+ dry_run: options.dryRun === true,
3173
+ ok: false,
3174
+ exit_code: exitCode,
3175
+ project_id: null,
3176
+ summary: { discovered: 0, applied: 0, skipped: 0 },
3177
+ items: [],
3178
+ warnings: [],
3179
+ errors: [{
3180
+ code: error instanceof UpdateWorkflowError ? error.code : "GENERAL_FAILURE",
3181
+ message
3182
+ }]
3183
+ }));
3184
+ }
3185
+ return exitCode;
3186
+ }
3187
+ }
3188
+
3189
+ // src/commands/recovery.ts
3190
+ import { stat as stat4 } from "node:fs/promises";
3191
+ import { join as join11 } from "node:path";
3192
+ async function exists4(path) {
3193
+ try {
3194
+ await stat4(path);
3195
+ return true;
3196
+ } catch {
3197
+ return false;
3198
+ }
3199
+ }
3200
+ function explicitConfigure(options) {
3201
+ return options.adapter !== void 0 || options.profile !== void 0 || options.config !== void 0 || options.serverUrl !== void 0 || options.tokenEnv !== void 0;
3202
+ }
3203
+ async function runRecoveryMenuIfApplicable(options, dependencies) {
3204
+ const pending = await pendingTransactions(dependencies.cwd);
3205
+ if (pending.length > 0) {
3206
+ if (options.nonInteractive === true) {
3207
+ dependencies.stderr("unfinished transaction requires interactive recovery\n");
3208
+ return 5;
3209
+ }
3210
+ const answer2 = await dependencies.prompt([
3211
+ "Unfinished Hunter Harness transaction detected.",
3212
+ "1. Recover last failed update",
3213
+ "2. Roll back last committed update",
3214
+ "3. View transaction status",
3215
+ "4. Clean old transactions",
3216
+ "Choose [1-4]: "
3217
+ ].join("\n"));
3218
+ try {
3219
+ if (answer2.trim() === "1") {
3220
+ const result = await recoverTransaction(
3221
+ dependencies.cwd,
3222
+ pending[0]?.transactionId ?? ""
3223
+ );
3224
+ dependencies.stdout("Recovery completed: " + result.status + ".\n");
3225
+ return 0;
3226
+ }
3227
+ if (answer2.trim() === "2") {
3228
+ const result = await rollbackLatestCommittedUpdate(dependencies.cwd);
3229
+ dependencies.stdout("Rollback completed: " + result.status + ".\n");
3230
+ return 0;
3231
+ }
3232
+ if (answer2.trim() === "3") {
3233
+ dependencies.stdout(JSON.stringify(
3234
+ await listTransactions(dependencies.cwd),
3235
+ null,
3236
+ 2
3237
+ ) + "\n");
3238
+ return 0;
3239
+ }
3240
+ if (answer2.trim() === "4") {
3241
+ const removed = await cleanupOldTransactions(dependencies.cwd);
3242
+ dependencies.stdout("Cleaned " + removed.length + " old transactions.\n");
3243
+ return 0;
3244
+ }
3245
+ return 2;
3246
+ } catch (error) {
3247
+ dependencies.stderr((error instanceof Error ? error.message : String(error)) + "\n");
3248
+ return 5;
3249
+ }
3250
+ }
3251
+ const initialized = await exists4(join11(dependencies.cwd, ".harness", "project.yaml"));
3252
+ if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
3253
+ return null;
3254
+ }
3255
+ const answer = await dependencies.prompt([
3256
+ "Hunter Harness project menu.",
3257
+ "1. Configure project",
3258
+ "2. Roll back last update",
3259
+ "3. Clean old transactions",
3260
+ "4. View transaction status",
3261
+ "5. Exit",
3262
+ "Choose [1-5]: "
3263
+ ].join("\n"));
3264
+ try {
3265
+ if (answer.trim() === "1") {
3266
+ return null;
3267
+ }
3268
+ if (answer.trim() === "2") {
3269
+ const result = await rollbackLatestCommittedUpdate(dependencies.cwd);
3270
+ dependencies.stdout("Rollback completed: " + result.status + ".\n");
3271
+ return 0;
3272
+ }
3273
+ if (answer.trim() === "3") {
3274
+ const removed = await cleanupOldTransactions(dependencies.cwd);
3275
+ dependencies.stdout("Cleaned " + removed.length + " old transactions.\n");
3276
+ return 0;
3277
+ }
3278
+ if (answer.trim() === "4") {
3279
+ dependencies.stdout(JSON.stringify(
3280
+ await listTransactions(dependencies.cwd),
3281
+ null,
3282
+ 2
3283
+ ) + "\n");
3284
+ return 0;
3285
+ }
3286
+ return answer.trim() === "5" ? 0 : 2;
3287
+ } catch (error) {
3288
+ dependencies.stderr((error instanceof Error ? error.message : String(error)) + "\n");
3289
+ return 5;
3290
+ }
3291
+ }
3292
+
3293
+ // src/bin.ts
3294
+ function defaultDependencies(overrides) {
3295
+ return {
3296
+ cwd: overrides.cwd ?? process.cwd(),
3297
+ resourcesRoot: overrides.resourcesRoot ?? fileURLToPath(
3298
+ new URL("../resources", import.meta.url)
3299
+ ),
3300
+ stdout: overrides.stdout ?? ((value) => process.stdout.write(value)),
3301
+ stderr: overrides.stderr ?? ((value) => process.stderr.write(value)),
3302
+ prompt: overrides.prompt ?? (async (question) => {
3303
+ const terminal = createInterface({ input: process.stdin, output: process.stdout });
3304
+ try {
3305
+ return await terminal.question(question);
3306
+ } finally {
3307
+ terminal.close();
3308
+ }
3309
+ }),
3310
+ fetch: overrides.fetch ?? globalThis.fetch,
3311
+ env: overrides.env ?? process.env
3312
+ };
3313
+ }
3314
+ function addCommonOptions(command) {
3315
+ return command.option("--dry-run").option("--yes").option("--json").option("--server-url <url>").option("--token-env <ENV_NAME>").option("--non-interactive");
3316
+ }
3317
+ async function runCli(argv, overrides = {}) {
3318
+ const dependencies = defaultDependencies(overrides);
3319
+ const program = addCommonOptions(new Command()).name("hunter-harness").description("Local-first, server-governed agent harness").option("--profile <name>").option("--config <file>").showHelpAfterError().exitOverride().configureOutput({
3320
+ writeOut: dependencies.stdout,
3321
+ writeErr: dependencies.stderr
3322
+ });
3323
+ let exitCode = 0;
3324
+ program.action(async (options) => {
3325
+ const recoveryResult = await runRecoveryMenuIfApplicable(options, dependencies);
3326
+ if (recoveryResult !== null) {
3327
+ exitCode = recoveryResult;
3328
+ return;
3329
+ }
3330
+ exitCode = await runConfigure(options, dependencies);
3331
+ });
3332
+ addCommonOptions(program.command("update")).description("Apply approved server artifacts").action(async (options) => {
3333
+ exitCode = await runUpdate(
3334
+ { ...program.opts(), ...options },
3335
+ dependencies
3336
+ );
3337
+ });
3338
+ addCommonOptions(program.command("push")).description("Create a governed proposal").action(async (options) => {
3339
+ exitCode = await runPush({ ...program.opts(), ...options }, dependencies);
3340
+ });
3341
+ try {
3342
+ await program.parseAsync(["node", "hunter-harness", ...argv]);
3343
+ return exitCode;
3344
+ } catch (error) {
3345
+ if (error instanceof CommanderError) {
3346
+ return error.code === "commander.helpDisplayed" ? 0 : 3;
3347
+ }
3348
+ throw error;
3349
+ }
3350
+ }
3351
+ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href) {
3352
+ process.exitCode = await runCli(process.argv.slice(2));
3353
+ }
3354
+ export {
3355
+ runCli
3356
+ };