@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
|
@@ -4,6 +4,7 @@ import { access, readFile } from "node:fs/promises";
|
|
|
4
4
|
import { constants as fsConstants } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { requiredGeneratedHarnessFilesForGovernance } from "./generated-harness-contract.mjs";
|
|
7
8
|
|
|
8
9
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
10
|
const models = {
|
|
@@ -16,120 +17,7 @@ const clientSupport = {
|
|
|
16
17
|
claudeHooks: {{CLIENT_CLAUDE_HOOKS_ENABLED}},
|
|
17
18
|
claudeSkills: {{CLIENT_CLAUDE_SKILLS_ENABLED}},
|
|
18
19
|
};
|
|
19
|
-
const
|
|
20
|
-
const claudeRequiredFiles = [
|
|
21
|
-
"CLAUDE.md",
|
|
22
|
-
".claude/CLAUDE.md",
|
|
23
|
-
".claude/settings.json",
|
|
24
|
-
"ai/model-overlays/anthropic/CLAUDE.md",
|
|
25
|
-
"workspace/CLAUDE.md",
|
|
26
|
-
"workspace/.claude/CLAUDE.md",
|
|
27
|
-
"workspace/.claude/settings.json",
|
|
28
|
-
"scripts/check-claude-compatibility.mjs",
|
|
29
|
-
];
|
|
30
|
-
const codexRequiredFiles = [
|
|
31
|
-
".codex/hooks.json",
|
|
32
|
-
"ai/contracts/codex-hooks.contract.json",
|
|
33
|
-
"scripts/check-codex-hooks.mjs",
|
|
34
|
-
"scripts/hooks/codex-hook.mjs",
|
|
35
|
-
"scripts/hooks/lib/codex-hooks-core.mjs",
|
|
36
|
-
];
|
|
37
|
-
const claudeRulesRequiredFiles = [".claude/rules/harness-client-surfaces.md", "workspace/.claude/rules/harness-client-surfaces.md"];
|
|
38
|
-
const driftFile = "scripts/check-overlay-drift.mjs";
|
|
39
|
-
const requiredFiles = [
|
|
40
|
-
"README.md",
|
|
41
|
-
"ai/AGENTS.md",
|
|
42
|
-
"ai/HUB.md",
|
|
43
|
-
"ai/context.md",
|
|
44
|
-
"ai/HARNESS.md",
|
|
45
|
-
"ai/HARNESS-ENGINEERING.md",
|
|
46
|
-
"ai/READINESS.md",
|
|
47
|
-
"ai/QUALITY.md",
|
|
48
|
-
"ai/DECISIONS.md",
|
|
49
|
-
"ai/PRODUCT-SUMMARY.md",
|
|
50
|
-
"ai/PRODUCT.md",
|
|
51
|
-
"ai/ARCHITECTURE.md",
|
|
52
|
-
"ai/DESIGN.md",
|
|
53
|
-
"ai/WORKFLOW.md",
|
|
54
|
-
"ai/VERSIONING.md",
|
|
55
|
-
"ai/CODEX-HOOKS.md",
|
|
56
|
-
"ai/RUNNER-SAFETY.md",
|
|
57
|
-
"ai/RUNNER-READINESS.md",
|
|
58
|
-
"ai/AGENT-GARBAGE-COLLECTION.md",
|
|
59
|
-
"ai/knowledge-manifest.json",
|
|
60
|
-
"ai/workspace/REPOS.md",
|
|
61
|
-
"ai/workspace/SYSTEM-MAP.md",
|
|
62
|
-
"ai/workspace/SESSION-BOOTSTRAP.md",
|
|
63
|
-
"ai/workspace/LOCAL-STACK.md",
|
|
64
|
-
"ai/workspace/TEST-STRATEGY.md",
|
|
65
|
-
"ai/contracts/README.md",
|
|
66
|
-
"ai/contracts/repo-boundaries.md",
|
|
67
|
-
"ai/contracts/repo-boundaries.contract.json",
|
|
68
|
-
"ai/contracts/app-legibility.md",
|
|
69
|
-
"ai/contracts/app-legibility.contract.json",
|
|
70
|
-
"ai/contracts/api-boundary.md",
|
|
71
|
-
"ai/contracts/api-boundary.contract.json",
|
|
72
|
-
"ai/contracts/security-boundary.md",
|
|
73
|
-
"ai/contracts/security-boundary.contract.json",
|
|
74
|
-
"ai/contracts/codex-hooks.md",
|
|
75
|
-
"ai/contracts/release-flow.md",
|
|
76
|
-
"ai/contracts/release-flow.contract.json",
|
|
77
|
-
"ai/contracts/github-safety.md",
|
|
78
|
-
"ai/contracts/github-safety.contract.json",
|
|
79
|
-
"ai/templates/README.md",
|
|
80
|
-
"ai/templates/task-brief-template.md",
|
|
81
|
-
"ai/templates/issue-template.md",
|
|
82
|
-
"ai/templates/fixtures/issues/valid-ready.md",
|
|
83
|
-
"ai/templates/fixtures/issues/invalid-placeholder.md",
|
|
84
|
-
"ai/templates/fixtures/issues/invalid-protected-surface.md",
|
|
85
|
-
"ai/skills/README.md",
|
|
86
|
-
"ai/skills/review-architecture.md",
|
|
87
|
-
"ai/skills/review-security.md",
|
|
88
|
-
"ai/skills/review-contract-drift.md",
|
|
89
|
-
"ai/skills/review-governance-drift.md",
|
|
90
|
-
"ai/plans/README.md",
|
|
91
|
-
"ai/plans/tech-debt.md",
|
|
92
|
-
"ai/specs/README.md",
|
|
93
|
-
"scripts/bootstrap-workspace.mjs",
|
|
94
|
-
"scripts/check-workspace.mjs",
|
|
95
|
-
"scripts/validate-governance.mjs",
|
|
96
|
-
"scripts/check-template-governance.mjs",
|
|
97
|
-
"scripts/check-readiness.mjs",
|
|
98
|
-
"scripts/check-task-template.mjs",
|
|
99
|
-
"scripts/check-issue-template.mjs",
|
|
100
|
-
"scripts/check-knowledge-manifest.mjs",
|
|
101
|
-
"scripts/check-plans.mjs",
|
|
102
|
-
"scripts/check-review-skills.mjs",
|
|
103
|
-
"scripts/check-garbage-collection.mjs",
|
|
104
|
-
"scripts/check-contract-manifests.mjs",
|
|
105
|
-
"scripts/generate-html-views.mjs",
|
|
106
|
-
"scripts/check-html-views.mjs",
|
|
107
|
-
"scripts/bootstrap-codex-worktree.mjs",
|
|
108
|
-
"scripts/check-worktrees.mjs",
|
|
109
|
-
"scripts/check-worktree-bootstrap-fixtures.mjs",
|
|
110
|
-
"scripts/lib/worktree-bootstrap.mjs",
|
|
111
|
-
"scripts/fixtures/worktrees/README.md",
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
if (models.openai) {
|
|
115
|
-
requiredFiles.push(...openaiRequiredFiles);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (models.anthropic) {
|
|
119
|
-
requiredFiles.push(...claudeRequiredFiles);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (clientSupport.codexHooks) {
|
|
123
|
-
requiredFiles.push(...codexRequiredFiles);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (clientSupport.claudeRules) {
|
|
127
|
-
requiredFiles.push(...claudeRulesRequiredFiles);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (models.openai || models.anthropic) {
|
|
131
|
-
requiredFiles.push(driftFile);
|
|
132
|
-
}
|
|
20
|
+
const requiredFiles = requiredGeneratedHarnessFilesForGovernance({ models, clientSupport });
|
|
133
21
|
|
|
134
22
|
async function exists(relativePath) {
|
|
135
23
|
try {
|
|
@@ -5,6 +5,12 @@ import { constants as fsConstants } from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { assertReferencesHarnessRoot } from "./lib/worktree-bootstrap.mjs";
|
|
8
|
+
import {
|
|
9
|
+
consumerEntrypointsForSettings,
|
|
10
|
+
requiredHarnessRepoFilesForWorkspaceCheck,
|
|
11
|
+
requiredWorkspaceFilesForWorkspaceCheck,
|
|
12
|
+
workspaceEntrypointsForSettings,
|
|
13
|
+
} from "./generated-harness-contract.mjs";
|
|
8
14
|
|
|
9
15
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
16
|
const workspaceRoot = path.resolve(repoRoot, "..");
|
|
@@ -18,93 +24,15 @@ const clientSupport = {
|
|
|
18
24
|
codexHooks: {{CLIENT_CODEX_HOOKS_ENABLED}},
|
|
19
25
|
claudeRules: {{CLIENT_CLAUDE_RULES_ENABLED}},
|
|
20
26
|
};
|
|
27
|
+
const settings = { models, clientSupport };
|
|
21
28
|
const harnessRepoNameError = "repo folder name: expected";
|
|
22
29
|
const missingEntryPrefix = "missing ";
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"ai/HARNESS-ENGINEERING.md",
|
|
30
|
-
"ai/READINESS.md",
|
|
31
|
-
"ai/QUALITY.md",
|
|
32
|
-
"ai/DECISIONS.md",
|
|
33
|
-
"ai/PRODUCT-SUMMARY.md",
|
|
34
|
-
"ai/PRODUCT.md",
|
|
35
|
-
"ai/ARCHITECTURE.md",
|
|
36
|
-
"ai/DESIGN.md",
|
|
37
|
-
"ai/WORKFLOW.md",
|
|
38
|
-
"ai/VERSIONING.md",
|
|
39
|
-
"ai/CODEX-HOOKS.md",
|
|
40
|
-
"ai/RUNNER-SAFETY.md",
|
|
41
|
-
"ai/RUNNER-READINESS.md",
|
|
42
|
-
"ai/AGENT-GARBAGE-COLLECTION.md",
|
|
43
|
-
"ai/knowledge-manifest.json",
|
|
44
|
-
"ai/workspace/REPOS.md",
|
|
45
|
-
"ai/workspace/SYSTEM-MAP.md",
|
|
46
|
-
"ai/workspace/SESSION-BOOTSTRAP.md",
|
|
47
|
-
"ai/workspace/LOCAL-STACK.md",
|
|
48
|
-
"ai/workspace/TEST-STRATEGY.md",
|
|
49
|
-
"ai/contracts/README.md",
|
|
50
|
-
"ai/templates/README.md",
|
|
51
|
-
"ai/skills/README.md",
|
|
52
|
-
"ai/specs/README.md",
|
|
53
|
-
"scripts/bootstrap-workspace.mjs",
|
|
54
|
-
"scripts/check-workspace.mjs",
|
|
55
|
-
"scripts/validate-governance.mjs",
|
|
56
|
-
"scripts/check-readiness.mjs",
|
|
57
|
-
"scripts/check-task-template.mjs",
|
|
58
|
-
"scripts/check-issue-template.mjs",
|
|
59
|
-
"scripts/check-knowledge-manifest.mjs",
|
|
60
|
-
"scripts/check-plans.mjs",
|
|
61
|
-
"scripts/check-review-skills.mjs",
|
|
62
|
-
"scripts/check-garbage-collection.mjs",
|
|
63
|
-
"scripts/check-contract-manifests.mjs",
|
|
64
|
-
"scripts/generate-html-views.mjs",
|
|
65
|
-
"scripts/check-html-views.mjs",
|
|
66
|
-
"scripts/bootstrap-codex-worktree.mjs",
|
|
67
|
-
"scripts/check-worktrees.mjs",
|
|
68
|
-
"scripts/check-worktree-bootstrap-fixtures.mjs",
|
|
69
|
-
];
|
|
70
|
-
const openaiRepoFiles = ["AGENTS.md", "ai/model-overlays/openai/AGENTS.md", "scripts/check-overlay-drift.mjs"];
|
|
71
|
-
const anthopicRepoFiles = [
|
|
72
|
-
"CLAUDE.md",
|
|
73
|
-
".claude/CLAUDE.md",
|
|
74
|
-
".claude/settings.json",
|
|
75
|
-
"ai/model-overlays/anthropic/CLAUDE.md",
|
|
76
|
-
"scripts/check-claude-compatibility.mjs",
|
|
77
|
-
"scripts/check-overlay-drift.mjs",
|
|
78
|
-
];
|
|
79
|
-
const codexRepoFiles = [".codex/hooks.json", "scripts/check-codex-hooks.mjs", "scripts/hooks/codex-hook.mjs"];
|
|
80
|
-
const claudeRulesRepoFiles = [".claude/rules/harness-client-surfaces.md"];
|
|
81
|
-
const workspaceOpenaiFiles = ["AGENTS.md"];
|
|
82
|
-
const workspaceAnthropicFiles = ["CLAUDE.md", ".claude/CLAUDE.md", ".claude/settings.json"];
|
|
83
|
-
const workspaceClaudeRulesFiles = [".claude/rules/harness-client-surfaces.md"];
|
|
84
|
-
const CLAUDE_MD = "CLAUDE.md";
|
|
85
|
-
|
|
86
|
-
const repoRequiredFiles = [...repoBaseFiles];
|
|
87
|
-
|
|
88
|
-
if (models.openai) {
|
|
89
|
-
repoRequiredFiles.push(...openaiRepoFiles);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (models.anthropic) {
|
|
93
|
-
repoRequiredFiles.push(...anthopicRepoFiles);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (clientSupport.codexHooks) {
|
|
97
|
-
repoRequiredFiles.push(...codexRepoFiles);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (clientSupport.claudeRules) {
|
|
101
|
-
repoRequiredFiles.push(...claudeRulesRepoFiles);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const workspaceRequiredFiles = [];
|
|
105
|
-
if (models.openai) workspaceRequiredFiles.push(...workspaceOpenaiFiles);
|
|
106
|
-
if (models.anthropic) workspaceRequiredFiles.push(...workspaceAnthropicFiles);
|
|
107
|
-
if (clientSupport.claudeRules) workspaceRequiredFiles.push(...workspaceClaudeRulesFiles);
|
|
30
|
+
const repoRequiredFiles = requiredHarnessRepoFilesForWorkspaceCheck(settings);
|
|
31
|
+
const workspaceRequiredFiles = requiredWorkspaceFilesForWorkspaceCheck(settings);
|
|
32
|
+
const workspaceRoutingEntrypoints = workspaceEntrypointsForSettings(settings).filter(
|
|
33
|
+
(entrypoint) => entrypoint.routing !== "presence",
|
|
34
|
+
);
|
|
35
|
+
const consumerRoutingEntrypoints = consumerEntrypointsForSettings(settings);
|
|
108
36
|
|
|
109
37
|
async function exists(filePath) {
|
|
110
38
|
try {
|
|
@@ -160,6 +88,13 @@ async function collectClaudeMemoryRoutingIssue({ basePath, relativePath }) {
|
|
|
160
88
|
});
|
|
161
89
|
}
|
|
162
90
|
|
|
91
|
+
async function collectEntrypointRoutingIssue({ basePath, entrypoint, expectedHarnessRoot }) {
|
|
92
|
+
if (entrypoint.routing === "claude-memory") {
|
|
93
|
+
return collectClaudeMemoryRoutingIssue({ basePath, relativePath: entrypoint.path });
|
|
94
|
+
}
|
|
95
|
+
return collectHarnessRoutingIssue({ basePath, relativePath: entrypoint.path, expectedHarnessRoot });
|
|
96
|
+
}
|
|
97
|
+
|
|
163
98
|
async function main() {
|
|
164
99
|
const missing = [];
|
|
165
100
|
if (path.basename(repoRoot) !== harnessRepoName) {
|
|
@@ -169,31 +104,20 @@ async function main() {
|
|
|
169
104
|
missing.push(...(await collectMissing(repoRoot, repoRequiredFiles, "repo")));
|
|
170
105
|
missing.push(...(await collectMissing(workspaceRoot, workspaceRequiredFiles, "workspace")));
|
|
171
106
|
|
|
107
|
+
for (const entrypoint of workspaceRoutingEntrypoints) {
|
|
108
|
+
const issue = await collectEntrypointRoutingIssue({ basePath: workspaceRoot, entrypoint, expectedHarnessRoot: repoRoot });
|
|
109
|
+
if (issue) missing.push(`workspace:${issue}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
172
112
|
for (const consumer of consumers) {
|
|
173
113
|
const consumerRoot = path.resolve(workspaceRoot, consumer.workspacePath);
|
|
174
114
|
if (!(await exists(consumerRoot))) {
|
|
175
115
|
missing.push(`consumer:${consumer.name}:missing repo at ${consumerRoot}`);
|
|
176
116
|
continue;
|
|
177
117
|
}
|
|
178
|
-
|
|
179
|
-
const issue = await
|
|
180
|
-
if (issue) missing.push(`workspace:${issue}`);
|
|
181
|
-
}
|
|
182
|
-
if (models.anthropic) {
|
|
183
|
-
const issue = await collectHarnessRoutingIssue({ basePath: workspaceRoot, relativePath: CLAUDE_MD, expectedHarnessRoot: repoRoot });
|
|
184
|
-
if (issue) missing.push(`workspace:${issue}`);
|
|
185
|
-
const memoryIssue = await collectClaudeMemoryRoutingIssue({ basePath: workspaceRoot, relativePath: ".claude/CLAUDE.md" });
|
|
186
|
-
if (memoryIssue) missing.push(`workspace:${memoryIssue}`);
|
|
187
|
-
}
|
|
188
|
-
if (models.openai) {
|
|
189
|
-
const issue = await collectHarnessRoutingIssue({ basePath: consumerRoot, relativePath: "AGENTS.md", expectedHarnessRoot: repoRoot });
|
|
190
|
-
if (issue) missing.push(`consumer:${consumer.name}:${issue}`);
|
|
191
|
-
}
|
|
192
|
-
if (models.anthropic) {
|
|
193
|
-
const issue = await collectHarnessRoutingIssue({ basePath: consumerRoot, relativePath: CLAUDE_MD, expectedHarnessRoot: repoRoot });
|
|
118
|
+
for (const entrypoint of consumerRoutingEntrypoints) {
|
|
119
|
+
const issue = await collectEntrypointRoutingIssue({ basePath: consumerRoot, entrypoint, expectedHarnessRoot: repoRoot });
|
|
194
120
|
if (issue) missing.push(`consumer:${consumer.name}:${issue}`);
|
|
195
|
-
const memoryIssue = await collectClaudeMemoryRoutingIssue({ basePath: consumerRoot, relativePath: ".claude/CLAUDE.md" });
|
|
196
|
-
if (memoryIssue) missing.push(`consumer:${consumer.name}:${memoryIssue}`);
|
|
197
121
|
}
|
|
198
122
|
}
|
|
199
123
|
|
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
classifyWorktreeBootstrap,
|
|
9
9
|
models,
|
|
10
10
|
parseWorktreeListPorcelain,
|
|
11
|
+
repairCommand,
|
|
11
12
|
requiredPointerFiles,
|
|
12
13
|
renderPointerFile,
|
|
14
|
+
shellQuote,
|
|
13
15
|
} from "./lib/worktree-bootstrap.mjs";
|
|
14
16
|
|
|
15
17
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -32,6 +34,8 @@ const tempConsumerRoot = "/tmp/{{PRIMARY_CONSUMER_NAME}}";
|
|
|
32
34
|
const testCaseTemplateConsumer = "{{PRIMARY_CONSUMER_NAME}}";
|
|
33
35
|
const invalidHarnessRoot = "Read ../{{HARNESS_REPO_NAME}}/AGENTS.md before editing.";
|
|
34
36
|
const wrongHarnessRootPointer = `Read /other/{{HARNESS_REPO_NAME}}/AGENTS.md before editing.`;
|
|
37
|
+
const unsafeHarnessRoot = "/workspace/{{HARNESS_REPO_NAME}} with spaces/'quotes';$(touch /tmp/unsafe)";
|
|
38
|
+
const unsafeCheckoutPath = "/workspace/{{PRIMARY_CONSUMER_NAME}} with spaces/'quotes';$(touch /tmp/checkout)";
|
|
35
39
|
const harnessFiles = new Set([
|
|
36
40
|
...(models.openai ? ["AGENTS.md"] : []),
|
|
37
41
|
...(models.anthropic ? ["CLAUDE.md"] : []),
|
|
@@ -115,8 +119,16 @@ assert.equal(mixedResult.state, stateMissing, "mixed_references");
|
|
|
115
119
|
|
|
116
120
|
const pointerPattern = new RegExp(`{{HARNESS_REPO_NAME}}/${models.openai ? "AGENTS" : "CLAUDE"}\\.md`);
|
|
117
121
|
const worktreePorcelainOutput = "worktree /repo/main\nHEAD abc\nbranch refs/heads/main\n\nworktree /repo/detached\nHEAD def\ndetached\n";
|
|
122
|
+
const quotedPointer = renderPointerFile({
|
|
123
|
+
relativePath: requiredPointerFiles[0],
|
|
124
|
+
harnessRoot: unsafeHarnessRoot,
|
|
125
|
+
repoName: testCaseTemplateConsumer,
|
|
126
|
+
});
|
|
127
|
+
const quotedBootstrapScript = path.join(path.resolve(unsafeHarnessRoot), "scripts/bootstrap-codex-worktree.mjs");
|
|
118
128
|
|
|
119
129
|
assert.match(renderPointerFile({ relativePath: requiredPointerFiles[0], harnessRoot, repoName: testCaseTemplateConsumer }), pointerPattern);
|
|
130
|
+
assert.match(quotedPointer, new RegExp(`node ${shellQuote(quotedBootstrapScript).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} ${shellQuote("<checkout-path>").replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`));
|
|
131
|
+
assert.equal(repairCommand({ harnessRoot: unsafeHarnessRoot, targetPath: unsafeCheckoutPath }), `node ${shellQuote(path.join(unsafeHarnessRoot, "scripts/bootstrap-codex-worktree.mjs"))} ${shellQuote(unsafeCheckoutPath)}`);
|
|
120
132
|
assert.equal(parseWorktreeListPorcelain(worktreePorcelainOutput).length, 2);
|
|
121
133
|
|
|
122
134
|
console.log("Worktree bootstrap fixture check passed.");
|