@structor-dev/cli 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +131 -21
- package/ROADMAP.md +38 -0
- package/SECURITY.md +33 -0
- package/bin/structor.mjs +561 -29
- package/contrib/self-harness/files/README.md +32 -0
- package/contrib/self-harness/files/ai/AGENTS.md +35 -0
- package/contrib/self-harness/files/ai/ARCHITECTURE.md +38 -0
- package/contrib/self-harness/files/ai/HUB.md +59 -0
- package/contrib/self-harness/files/ai/PRODUCT.md +36 -0
- package/contrib/self-harness/files/ai/QUALITY.md +31 -0
- package/contrib/self-harness/files/ai/context.md +38 -0
- package/contrib/self-harness/files/scripts/check-workspace.mjs +72 -0
- package/contrib/self-harness/harness.config.json +37 -0
- package/docs/CONTRIBUTOR-SETUP.md +45 -0
- package/docs/INIT.md +55 -2
- package/docs/public-launch.md +150 -0
- package/examples/anthropic-only/harness.config.json +26 -0
- package/examples/frontend-backend/harness.config.json +8 -8
- package/examples/generated-harness-tree.md +432 -0
- package/examples/openai-and-anthropic/harness.config.json +7 -7
- package/examples/single-repo/harness.config.json +7 -7
- package/harness.config.example.json +1 -1
- package/package.json +12 -4
- package/schemas/contract-manifest.schema.json +0 -1
- package/schemas/harness-config.schema.json +5 -2
- package/scripts/check-config.mjs +20 -31
- package/scripts/check-examples.mjs +146 -0
- package/scripts/check-placeholders.mjs +2 -0
- package/scripts/check-public-hygiene.mjs +249 -0
- package/scripts/check-schemas.mjs +42 -0
- package/scripts/check-template-files.mjs +15 -98
- package/scripts/generated-harness-contract.mjs +416 -0
- package/scripts/init-harness.mjs +227 -139
- package/scripts/lib.mjs +462 -12
- package/scripts/rendered-config.mjs +109 -0
- package/scripts/setup-contributor.mjs +125 -0
- package/scripts/smoke-template.mjs +260 -73
- package/template/AGENTS.md.tpl +4 -2
- package/template/README.md.tpl +5 -0
- package/template/ai/CODEX-HOOKS.md.tpl +1 -1
- package/template/ai/HARNESS-ENGINEERING.md.tpl +5 -2
- package/template/ai/HARNESS.md.tpl +4 -1
- package/template/ai/contracts/codex-hooks.contract.json.tpl +58 -1
- package/template/ai/contracts/codex-hooks.md.tpl +6 -0
- package/template/ai/contracts/release-flow.md.tpl +1 -1
- package/template/ai/templates/fixtures/issues/valid-ready.md.tpl +3 -1
- package/template/ai/templates/issue-template.md.tpl +3 -1
- package/template/ai/workspace/LOCAL-STACK.md.tpl +1 -1
- package/template/ai/workspace/SYSTEM-MAP.md.tpl +2 -2
- package/template/consumer/AGENTS.md.tpl +4 -4
- package/template/consumer/CLAUDE.md.tpl +4 -4
- package/template/scripts/bootstrap-workspace.mjs.tpl +11 -25
- package/template/scripts/check-claude-compatibility.mjs.tpl +62 -9
- package/template/scripts/check-codex-hooks.mjs.tpl +262 -20
- package/template/scripts/check-template-governance.mjs.tpl +2 -114
- package/template/scripts/check-workspace.mjs.tpl +27 -103
- package/template/scripts/check-worktree-bootstrap-fixtures.mjs.tpl +12 -0
- package/template/scripts/generate-html-views.mjs.tpl +357 -56
- package/template/scripts/generated-harness-contract.mjs.tpl +1 -0
- package/template/scripts/hooks/lib/codex-hooks-core.mjs.tpl +14 -3
- package/template/scripts/lib/path-safety.mjs.tpl +87 -0
- package/template/scripts/lib/worktree-bootstrap.mjs.tpl +16 -13
- package/template/scripts/validate-governance.mjs.tpl +52 -36
- package/schemas/task-brief.schema.json +0 -37
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
const gateModelOpenai = "model:openai";
|
|
2
|
+
const gateModelAnthropic = "model:anthropic";
|
|
3
|
+
const gateClientCodexHooks = "client:codexHooks";
|
|
4
|
+
const gateClientClaudeRules = "client:claudeRules";
|
|
5
|
+
const gateAnyModel = "model:any";
|
|
6
|
+
|
|
7
|
+
export const generatedHarnessContractScript = "scripts/generated-harness-contract.mjs";
|
|
8
|
+
|
|
9
|
+
function artifact(template, options = {}) {
|
|
10
|
+
return {
|
|
11
|
+
generated: true,
|
|
12
|
+
gates: [],
|
|
13
|
+
...options,
|
|
14
|
+
template,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function script(template, options = {}) {
|
|
19
|
+
return artifact(template, {
|
|
20
|
+
trustedScript: true,
|
|
21
|
+
...options,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const generatedHarnessArtifacts = [
|
|
26
|
+
artifact("AGENTS.md.tpl", { gates: [gateModelOpenai], workspaceCheck: "repo" }),
|
|
27
|
+
artifact("CLAUDE.md.tpl", { gates: [gateModelAnthropic], workspaceCheck: "repo", claudeCompatibility: true }),
|
|
28
|
+
artifact(".claude/CLAUDE.md.tpl", {
|
|
29
|
+
gates: [gateModelAnthropic],
|
|
30
|
+
workspaceCheck: "repo",
|
|
31
|
+
claudeCompatibility: true,
|
|
32
|
+
}),
|
|
33
|
+
artifact(".claude/rules/harness-client-surfaces.md.tpl", {
|
|
34
|
+
gates: [gateClientClaudeRules],
|
|
35
|
+
workspaceCheck: "repo",
|
|
36
|
+
claudeCompatibility: true,
|
|
37
|
+
}),
|
|
38
|
+
artifact(".claude/settings.json.tpl", {
|
|
39
|
+
gates: [gateModelAnthropic],
|
|
40
|
+
workspaceCheck: "repo",
|
|
41
|
+
claudeCompatibility: true,
|
|
42
|
+
}),
|
|
43
|
+
artifact(".codex/hooks.json.tpl", { gates: [gateClientCodexHooks], workspaceCheck: "repo" }),
|
|
44
|
+
artifact("README.md.tpl", { workspaceCheck: "repo" }),
|
|
45
|
+
artifact("ai/AGENTS.md.tpl", { workspaceCheck: "repo" }),
|
|
46
|
+
artifact("ai/HUB.md.tpl", { workspaceCheck: "repo" }),
|
|
47
|
+
artifact("ai/context.md.tpl", { workspaceCheck: "repo" }),
|
|
48
|
+
artifact("ai/HARNESS.md.tpl", { workspaceCheck: "repo" }),
|
|
49
|
+
artifact("ai/HARNESS-ENGINEERING.md.tpl", { workspaceCheck: "repo" }),
|
|
50
|
+
artifact("ai/READINESS.md.tpl", { workspaceCheck: "repo" }),
|
|
51
|
+
artifact("ai/QUALITY.md.tpl", { workspaceCheck: "repo" }),
|
|
52
|
+
artifact("ai/DECISIONS.md.tpl", { workspaceCheck: "repo" }),
|
|
53
|
+
artifact("ai/PRODUCT-SUMMARY.md.tpl", { workspaceCheck: "repo" }),
|
|
54
|
+
artifact("ai/PRODUCT.md.tpl", { workspaceCheck: "repo" }),
|
|
55
|
+
artifact("ai/ARCHITECTURE.md.tpl", { workspaceCheck: "repo" }),
|
|
56
|
+
artifact("ai/DESIGN.md.tpl", { workspaceCheck: "repo" }),
|
|
57
|
+
artifact("ai/WORKFLOW.md.tpl", { workspaceCheck: "repo" }),
|
|
58
|
+
artifact("ai/VERSIONING.md.tpl", { workspaceCheck: "repo" }),
|
|
59
|
+
artifact("ai/CODEX-HOOKS.md.tpl", { workspaceCheck: "repo" }),
|
|
60
|
+
artifact("ai/RUNNER-SAFETY.md.tpl", { workspaceCheck: "repo" }),
|
|
61
|
+
artifact("ai/RUNNER-READINESS.md.tpl", { workspaceCheck: "repo" }),
|
|
62
|
+
artifact("ai/AGENT-GARBAGE-COLLECTION.md.tpl", { workspaceCheck: "repo" }),
|
|
63
|
+
artifact("ai/knowledge-manifest.json.tpl", { workspaceCheck: "repo" }),
|
|
64
|
+
artifact("ai/workspace/REPOS.md.tpl", { workspaceCheck: "repo" }),
|
|
65
|
+
artifact("ai/workspace/SYSTEM-MAP.md.tpl", { workspaceCheck: "repo" }),
|
|
66
|
+
artifact("ai/workspace/SESSION-BOOTSTRAP.md.tpl", { workspaceCheck: "repo" }),
|
|
67
|
+
artifact("ai/workspace/LOCAL-STACK.md.tpl", { workspaceCheck: "repo" }),
|
|
68
|
+
artifact("ai/workspace/TEST-STRATEGY.md.tpl", { workspaceCheck: "repo" }),
|
|
69
|
+
artifact("ai/model-overlays/openai/AGENTS.md.tpl", { gates: [gateModelOpenai], workspaceCheck: "repo" }),
|
|
70
|
+
artifact("ai/model-overlays/anthropic/CLAUDE.md.tpl", {
|
|
71
|
+
gates: [gateModelAnthropic],
|
|
72
|
+
workspaceCheck: "repo",
|
|
73
|
+
}),
|
|
74
|
+
artifact("consumer/AGENTS.md.tpl", {
|
|
75
|
+
generated: false,
|
|
76
|
+
gates: [gateModelOpenai],
|
|
77
|
+
consumerEntrypoint: { path: "AGENTS.md", routing: "harness", model: "openai" },
|
|
78
|
+
}),
|
|
79
|
+
artifact("consumer/CLAUDE.md.tpl", {
|
|
80
|
+
generated: false,
|
|
81
|
+
gates: [gateModelAnthropic],
|
|
82
|
+
consumerEntrypoint: { path: "CLAUDE.md", routing: "harness", model: "anthropic" },
|
|
83
|
+
}),
|
|
84
|
+
artifact("consumer/.claude/CLAUDE.md.tpl", {
|
|
85
|
+
generated: false,
|
|
86
|
+
gates: [gateModelAnthropic],
|
|
87
|
+
consumerEntrypoint: { path: ".claude/CLAUDE.md", routing: "claude-memory", model: "anthropic" },
|
|
88
|
+
}),
|
|
89
|
+
artifact("workspace/AGENTS.md.tpl", {
|
|
90
|
+
gates: [gateModelOpenai],
|
|
91
|
+
workspaceEntrypoint: { path: "AGENTS.md", routing: "harness", model: "openai" },
|
|
92
|
+
}),
|
|
93
|
+
artifact("workspace/CLAUDE.md.tpl", {
|
|
94
|
+
gates: [gateModelAnthropic],
|
|
95
|
+
workspaceEntrypoint: { path: "CLAUDE.md", routing: "harness", model: "anthropic" },
|
|
96
|
+
}),
|
|
97
|
+
artifact("workspace/.claude/CLAUDE.md.tpl", {
|
|
98
|
+
gates: [gateModelAnthropic],
|
|
99
|
+
workspaceEntrypoint: { path: ".claude/CLAUDE.md", routing: "claude-memory", model: "anthropic" },
|
|
100
|
+
}),
|
|
101
|
+
artifact("workspace/.claude/rules/harness-client-surfaces.md.tpl", {
|
|
102
|
+
gates: [gateClientClaudeRules],
|
|
103
|
+
workspaceEntrypoint: { path: ".claude/rules/harness-client-surfaces.md", routing: "presence", model: "anthropic" },
|
|
104
|
+
}),
|
|
105
|
+
artifact("workspace/.claude/settings.json.tpl", {
|
|
106
|
+
gates: [gateModelAnthropic],
|
|
107
|
+
workspaceEntrypoint: { path: ".claude/settings.json", routing: "presence", model: "anthropic" },
|
|
108
|
+
}),
|
|
109
|
+
artifact("ai/contracts/README.md.tpl", { workspaceCheck: "repo" }),
|
|
110
|
+
artifact("ai/contracts/repo-boundaries.md.tpl"),
|
|
111
|
+
artifact("ai/contracts/app-legibility.md.tpl"),
|
|
112
|
+
artifact("ai/contracts/api-boundary.md.tpl"),
|
|
113
|
+
artifact("ai/contracts/security-boundary.md.tpl"),
|
|
114
|
+
artifact("ai/contracts/repo-boundaries.contract.json.tpl"),
|
|
115
|
+
artifact("ai/contracts/app-legibility.contract.json.tpl"),
|
|
116
|
+
artifact("ai/contracts/api-boundary.contract.json.tpl"),
|
|
117
|
+
artifact("ai/contracts/security-boundary.contract.json.tpl"),
|
|
118
|
+
artifact("ai/contracts/codex-hooks.md.tpl"),
|
|
119
|
+
artifact("ai/contracts/codex-hooks.contract.json.tpl", { gates: [gateClientCodexHooks] }),
|
|
120
|
+
artifact("ai/contracts/release-flow.md.tpl"),
|
|
121
|
+
artifact("ai/contracts/release-flow.contract.json.tpl"),
|
|
122
|
+
artifact("ai/contracts/github-safety.md.tpl"),
|
|
123
|
+
artifact("ai/contracts/github-safety.contract.json.tpl"),
|
|
124
|
+
artifact("ai/templates/README.md.tpl", { workspaceCheck: "repo" }),
|
|
125
|
+
artifact("ai/templates/task-brief-template.md.tpl"),
|
|
126
|
+
artifact("ai/templates/issue-template.md.tpl"),
|
|
127
|
+
artifact("ai/templates/fixtures/issues/valid-ready.md.tpl"),
|
|
128
|
+
artifact("ai/templates/fixtures/issues/invalid-placeholder.md.tpl"),
|
|
129
|
+
artifact("ai/templates/fixtures/issues/invalid-protected-surface.md.tpl"),
|
|
130
|
+
artifact("ai/specs/README.md.tpl", { workspaceCheck: "repo" }),
|
|
131
|
+
artifact("ai/skills/README.md.tpl", { workspaceCheck: "repo" }),
|
|
132
|
+
artifact("ai/skills/review-architecture.md.tpl"),
|
|
133
|
+
artifact("ai/skills/review-security.md.tpl"),
|
|
134
|
+
artifact("ai/skills/review-contract-drift.md.tpl"),
|
|
135
|
+
artifact("ai/skills/review-governance-drift.md.tpl"),
|
|
136
|
+
artifact("ai/plans/README.md.tpl"),
|
|
137
|
+
artifact("ai/plans/tech-debt.md.tpl"),
|
|
138
|
+
script("scripts/generated-harness-contract.mjs.tpl", { workspaceCheck: "repo" }),
|
|
139
|
+
script("scripts/validate-governance.mjs.tpl", { trustedScript: false, workspaceCheck: "repo" }),
|
|
140
|
+
script("scripts/check-template-governance.mjs.tpl"),
|
|
141
|
+
script("scripts/check-readiness.mjs.tpl", { workspaceCheck: "repo" }),
|
|
142
|
+
script("scripts/check-task-template.mjs.tpl", { workspaceCheck: "repo" }),
|
|
143
|
+
script("scripts/check-issue-template.mjs.tpl"),
|
|
144
|
+
script("scripts/check-knowledge-manifest.mjs.tpl", { workspaceCheck: "repo" }),
|
|
145
|
+
script("scripts/check-plans.mjs.tpl"),
|
|
146
|
+
script("scripts/check-review-skills.mjs.tpl"),
|
|
147
|
+
script("scripts/check-garbage-collection.mjs.tpl"),
|
|
148
|
+
script("scripts/check-contract-manifests.mjs.tpl", { workspaceCheck: "repo" }),
|
|
149
|
+
script("scripts/generate-html-views.mjs.tpl", { workspaceCheck: "repo", postRender: "executeOnFreshRender" }),
|
|
150
|
+
script("scripts/check-html-views.mjs.tpl", { workspaceCheck: "repo" }),
|
|
151
|
+
script("scripts/check-codex-hooks.mjs.tpl", { gates: [gateClientCodexHooks], workspaceCheck: "repo" }),
|
|
152
|
+
script("scripts/check-claude-compatibility.mjs.tpl", {
|
|
153
|
+
gates: [gateModelAnthropic],
|
|
154
|
+
workspaceCheck: "repo",
|
|
155
|
+
}),
|
|
156
|
+
script("scripts/check-overlay-drift.mjs.tpl", { gates: [gateAnyModel], workspaceCheck: "repo" }),
|
|
157
|
+
script("scripts/bootstrap-codex-worktree.mjs.tpl", { workspaceCheck: "repo" }),
|
|
158
|
+
script("scripts/check-worktrees.mjs.tpl", { workspaceCheck: "repo" }),
|
|
159
|
+
script("scripts/check-worktree-bootstrap-fixtures.mjs.tpl", { workspaceCheck: "repo" }),
|
|
160
|
+
script("scripts/lib/path-safety.mjs.tpl", { workspaceCheck: "repo" }),
|
|
161
|
+
script("scripts/lib/worktree-bootstrap.mjs.tpl", { workspaceCheck: "repo" }),
|
|
162
|
+
artifact("scripts/fixtures/worktrees/README.md.tpl", { workspaceCheck: "repo" }),
|
|
163
|
+
script("scripts/bootstrap-workspace.mjs.tpl", { workspaceCheck: "repo" }),
|
|
164
|
+
script("scripts/check-workspace.mjs.tpl", { workspaceCheck: "repo" }),
|
|
165
|
+
script("scripts/hooks/codex-hook.mjs.tpl", { gates: [gateClientCodexHooks], workspaceCheck: "repo" }),
|
|
166
|
+
script("scripts/hooks/lib/codex-hooks-core.mjs.tpl", { gates: [gateClientCodexHooks], workspaceCheck: "repo" }),
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
export const generatedHarnessValidationChecks = [
|
|
170
|
+
{ path: "scripts/check-template-governance.mjs", phase: "required", dependencies: [generatedHarnessContractScript] },
|
|
171
|
+
{ path: "scripts/check-task-template.mjs", phase: "required" },
|
|
172
|
+
{ path: "scripts/check-readiness.mjs", phase: "required" },
|
|
173
|
+
{ path: "scripts/check-issue-template.mjs", phase: "required" },
|
|
174
|
+
{ path: "scripts/check-knowledge-manifest.mjs", phase: "required" },
|
|
175
|
+
{ path: "scripts/check-plans.mjs", phase: "required" },
|
|
176
|
+
{ path: "scripts/check-review-skills.mjs", phase: "required" },
|
|
177
|
+
{ path: "scripts/check-garbage-collection.mjs", phase: "required" },
|
|
178
|
+
{ path: "scripts/check-contract-manifests.mjs", phase: "required" },
|
|
179
|
+
{
|
|
180
|
+
path: "scripts/check-html-views.mjs",
|
|
181
|
+
phase: "required",
|
|
182
|
+
dependencies: ["scripts/generate-html-views.mjs", "scripts/lib/path-safety.mjs"],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
path: "scripts/check-worktree-bootstrap-fixtures.mjs",
|
|
186
|
+
phase: "required",
|
|
187
|
+
dependencies: ["scripts/lib/path-safety.mjs", "scripts/lib/worktree-bootstrap.mjs"],
|
|
188
|
+
},
|
|
189
|
+
{ path: "scripts/check-repo-name-consistency.mjs", optional: true },
|
|
190
|
+
{ path: "scripts/check-linear-contract.mjs", optional: true },
|
|
191
|
+
{ path: "scripts/check-contract-conformance.mjs", optional: true },
|
|
192
|
+
{ path: "scripts/check-domain-contract-matrix.mjs", optional: true },
|
|
193
|
+
{
|
|
194
|
+
path: "scripts/check-codex-hooks.mjs",
|
|
195
|
+
phase: "conditional",
|
|
196
|
+
gates: [gateClientCodexHooks],
|
|
197
|
+
dependencies: ["scripts/hooks/codex-hook.mjs", "scripts/hooks/lib/codex-hooks-core.mjs"],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
path: "scripts/check-claude-compatibility.mjs",
|
|
201
|
+
phase: "conditional",
|
|
202
|
+
gates: [gateModelAnthropic],
|
|
203
|
+
dependencies: [generatedHarnessContractScript],
|
|
204
|
+
},
|
|
205
|
+
{ path: "scripts/check-overlay-drift.mjs", phase: "conditional", gates: [gateAnyModel] },
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
export function clientSupportForConfig(config) {
|
|
209
|
+
return {
|
|
210
|
+
codexHooks: Boolean(config.models?.openai) && (config.clientSupport?.codex?.hooks ?? true),
|
|
211
|
+
claudeRules: Boolean(config.models?.anthropic) && (config.clientSupport?.claude?.rules ?? true),
|
|
212
|
+
claudeHooks: Boolean(config.models?.anthropic) && (config.clientSupport?.claude?.hooks ?? false),
|
|
213
|
+
claudeSkills: Boolean(config.models?.anthropic) && (config.clientSupport?.claude?.skills ?? false),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function normalizeHarnessSettings(input) {
|
|
218
|
+
const models = {
|
|
219
|
+
openai: Boolean(input.models?.openai),
|
|
220
|
+
anthropic: Boolean(input.models?.anthropic),
|
|
221
|
+
};
|
|
222
|
+
const support = input.clientSupport ?? {};
|
|
223
|
+
const normalizedSupport =
|
|
224
|
+
Object.hasOwn(support, "codexHooks") ||
|
|
225
|
+
Object.hasOwn(support, "claudeRules") ||
|
|
226
|
+
Object.hasOwn(support, "claudeHooks") ||
|
|
227
|
+
Object.hasOwn(support, "claudeSkills")
|
|
228
|
+
? {
|
|
229
|
+
codexHooks: Boolean(support.codexHooks),
|
|
230
|
+
claudeRules: Boolean(support.claudeRules),
|
|
231
|
+
claudeHooks: Boolean(support.claudeHooks),
|
|
232
|
+
claudeSkills: Boolean(support.claudeSkills),
|
|
233
|
+
}
|
|
234
|
+
: clientSupportForConfig({ models, clientSupport: support });
|
|
235
|
+
|
|
236
|
+
return { models, clientSupport: normalizedSupport };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function gateEnabled(gate, settings) {
|
|
240
|
+
if (gate === gateModelOpenai) return settings.models.openai;
|
|
241
|
+
if (gate === gateModelAnthropic) return settings.models.anthropic;
|
|
242
|
+
if (gate === gateClientCodexHooks) return settings.clientSupport.codexHooks;
|
|
243
|
+
if (gate === gateClientClaudeRules) return settings.clientSupport.claudeRules;
|
|
244
|
+
if (gate === gateAnyModel) return settings.models.openai || settings.models.anthropic;
|
|
245
|
+
throw new Error(`Unknown generated harness gate: ${gate}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function gatesEnabled(gates, input) {
|
|
249
|
+
const settings = normalizeHarnessSettings(input);
|
|
250
|
+
return (gates ?? []).every((gate) => gateEnabled(gate, settings));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function artifactEnabled(artifactContract, input) {
|
|
254
|
+
return gatesEnabled(artifactContract.gates, input);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function artifactTargetPath(artifactContract) {
|
|
258
|
+
return artifactContract.target ?? artifactContract.template.replace(/\.tpl$/, "");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function templateArtifactPath(artifactContract) {
|
|
262
|
+
return `template/${artifactContract.template}`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function generatedHarnessTemplatePaths() {
|
|
266
|
+
return generatedHarnessArtifacts.map(templateArtifactPath).sort();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function contractArtifactForTemplate(sourceRelative) {
|
|
270
|
+
return generatedHarnessArtifacts.find((artifactContract) => artifactContract.template === sourceRelative) ?? null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function shouldRenderTemplate(sourceRelative, input) {
|
|
274
|
+
const artifactContract = contractArtifactForTemplate(sourceRelative);
|
|
275
|
+
return Boolean(artifactContract?.generated && artifactEnabled(artifactContract, input));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function enabledGeneratedArtifacts(input) {
|
|
279
|
+
return generatedHarnessArtifacts.filter(
|
|
280
|
+
(artifactContract) => artifactContract.generated && artifactEnabled(artifactContract, input),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function requiredGeneratedHarnessFilesForGovernance(input) {
|
|
285
|
+
return enabledGeneratedArtifacts(input).map(artifactTargetPath);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function requiredHarnessRepoFilesForWorkspaceCheck(input) {
|
|
289
|
+
return enabledGeneratedArtifacts(input)
|
|
290
|
+
.filter((artifactContract) => artifactContract.workspaceCheck === "repo")
|
|
291
|
+
.map(artifactTargetPath);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function entrypointsForSettings(input, key) {
|
|
295
|
+
return generatedHarnessArtifacts
|
|
296
|
+
.filter((artifactContract) => artifactEnabled(artifactContract, input) && artifactContract[key])
|
|
297
|
+
.map((artifactContract) => ({
|
|
298
|
+
source: artifactTargetPath(artifactContract),
|
|
299
|
+
template: artifactContract.template,
|
|
300
|
+
...artifactContract[key],
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function workspaceEntrypointsForSettings(input) {
|
|
305
|
+
return entrypointsForSettings(input, "workspaceEntrypoint");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function consumerEntrypointsForSettings(input) {
|
|
309
|
+
return entrypointsForSettings(input, "consumerEntrypoint");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function requiredWorkspaceFilesForWorkspaceCheck(input) {
|
|
313
|
+
return workspaceEntrypointsForSettings(input).map((entrypoint) => entrypoint.path);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function requiredClaudeCompatibilityFiles(input) {
|
|
317
|
+
return enabledGeneratedArtifacts(input)
|
|
318
|
+
.filter((artifactContract) => artifactContract.claudeCompatibility)
|
|
319
|
+
.map(artifactTargetPath);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function trustedGeneratedScriptTemplatesForSettings(input) {
|
|
323
|
+
return enabledGeneratedArtifacts(input)
|
|
324
|
+
.filter((artifactContract) => artifactContract.trustedScript)
|
|
325
|
+
.map((artifactContract) => artifactContract.template);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function trustedGeneratedScriptTargetsForSettings(input) {
|
|
329
|
+
return enabledGeneratedArtifacts(input)
|
|
330
|
+
.filter((artifactContract) => artifactContract.trustedScript)
|
|
331
|
+
.map(artifactTargetPath);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function freshRenderScriptTemplatesForSettings(input) {
|
|
335
|
+
return enabledGeneratedArtifacts(input)
|
|
336
|
+
.filter((artifactContract) => artifactContract.postRender === "executeOnFreshRender")
|
|
337
|
+
.map((artifactContract) => artifactContract.template);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function validationPlanForSettings(input) {
|
|
341
|
+
const enabledChecks = generatedHarnessValidationChecks.filter((check) => gatesEnabled(check.gates, input));
|
|
342
|
+
const requiredChecks = enabledChecks
|
|
343
|
+
.filter((check) => !check.optional && check.phase !== "conditional")
|
|
344
|
+
.map((check) => check.path);
|
|
345
|
+
const optionalChecks = enabledChecks.filter((check) => check.optional).map((check) => check.path);
|
|
346
|
+
const conditionalChecks = enabledChecks
|
|
347
|
+
.filter((check) => !check.optional && check.phase === "conditional")
|
|
348
|
+
.map((check) => check.path);
|
|
349
|
+
const checkDependencies = Object.fromEntries(
|
|
350
|
+
enabledChecks
|
|
351
|
+
.filter((check) => check.dependencies?.length)
|
|
352
|
+
.map((check) => [check.path, check.dependencies]),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
return { requiredChecks, optionalChecks, conditionalChecks, checkDependencies };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function generatedHarnessContractErrors() {
|
|
359
|
+
const errors = [];
|
|
360
|
+
const templates = new Map();
|
|
361
|
+
const generatedTargets = new Map();
|
|
362
|
+
const validationChecks = new Map();
|
|
363
|
+
|
|
364
|
+
for (const artifactContract of generatedHarnessArtifacts) {
|
|
365
|
+
if (!artifactContract.template.endsWith(".tpl")) {
|
|
366
|
+
errors.push(`${artifactContract.template} must declare a .tpl template path.`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const previousTemplate = templates.get(artifactContract.template);
|
|
370
|
+
if (previousTemplate) {
|
|
371
|
+
errors.push(`${artifactContract.template} is declared more than once.`);
|
|
372
|
+
}
|
|
373
|
+
templates.set(artifactContract.template, artifactContract);
|
|
374
|
+
|
|
375
|
+
if (artifactContract.generated) {
|
|
376
|
+
const target = artifactTargetPath(artifactContract);
|
|
377
|
+
const previousTarget = generatedTargets.get(target);
|
|
378
|
+
if (previousTarget) {
|
|
379
|
+
errors.push(`${target} is generated by both ${previousTarget.template} and ${artifactContract.template}.`);
|
|
380
|
+
}
|
|
381
|
+
generatedTargets.set(target, artifactContract);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (artifactContract.template.startsWith("scripts/") && artifactContract.template.endsWith(".mjs.tpl")) {
|
|
385
|
+
if (typeof artifactContract.trustedScript !== "boolean") {
|
|
386
|
+
errors.push(`${artifactContract.template} must declare trustedScript true or false.`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const check of generatedHarnessValidationChecks) {
|
|
392
|
+
if (validationChecks.has(check.path)) {
|
|
393
|
+
errors.push(`${check.path} is declared more than once in generated harness validation checks.`);
|
|
394
|
+
}
|
|
395
|
+
validationChecks.set(check.path, check);
|
|
396
|
+
|
|
397
|
+
const checkArtifact = generatedTargets.get(check.path);
|
|
398
|
+
if (!check.optional && !checkArtifact) {
|
|
399
|
+
errors.push(`${check.path} must have a generated artifact declaration.`);
|
|
400
|
+
}
|
|
401
|
+
if (checkArtifact && !checkArtifact.trustedScript) {
|
|
402
|
+
errors.push(`${check.path} must be declared as a trusted generated script.`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for (const dependency of check.dependencies ?? []) {
|
|
406
|
+
const dependencyArtifact = generatedTargets.get(dependency);
|
|
407
|
+
if (!dependencyArtifact) {
|
|
408
|
+
errors.push(`${check.path} depends on ${dependency}, but ${dependency} has no generated artifact declaration.`);
|
|
409
|
+
} else if (!dependencyArtifact.trustedScript) {
|
|
410
|
+
errors.push(`${check.path} depends on ${dependency}, but ${dependency} is not a trusted generated script.`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return errors;
|
|
416
|
+
}
|