harness-everything 0.2.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 +7 -0
- package/build-multi-agent-system/SKILL.md +68 -68
- package/create-agent-launcher/SKILL.md +48 -48
- package/docs/workflows/build-multi-agent-system.md +62 -62
- package/docs/workflows/create-agent-launcher.md +62 -62
- package/docs/workflows/environment-detection.md +63 -63
- package/docs/workflows/eval-harness.md +57 -57
- package/docs/workflows/fable-discipline.md +59 -59
- package/docs/workflows/fable-mode.md +32 -32
- package/docs/workflows/git-commit.md +62 -62
- package/docs/workflows/grill-me.md +62 -62
- package/docs/workflows/grill-with-docs.md +62 -62
- package/docs/workflows/improve-codebase-architecture.md +60 -60
- package/docs/workflows/repo-docs.md +59 -59
- package/docs/workflows/rewrite-commits.md +60 -60
- package/docs/workflows/security-review.md +58 -58
- package/docs/workflows/self-evolve.md +59 -59
- package/docs/workflows/tdd.md +39 -39
- package/docs/workflows/todo-driven-workflow.md +36 -36
- package/docs/workflows/using-git-worktrees.md +63 -63
- package/docs/workflows/verification-loop.md +69 -69
- package/docs/workflows/verify-before-claim.md +60 -60
- package/docs/workflows/zoom-out.md +62 -62
- package/environment-detection/SKILL.md +77 -77
- package/eval-framework/behavioral-test.js +105 -105
- package/eval-framework/mechanism-test.js +251 -251
- package/eval-harness/SKILL.md +59 -59
- package/fable-discipline/SKILL.md +38 -38
- package/fable-mode/SKILL.md +51 -51
- package/git-commit/SKILL.md +55 -55
- package/grill-me/SKILL.md +36 -36
- package/grill-with-docs/SKILL.md +99 -99
- package/harness-everything/SKILL.md +141 -141
- package/harness-everything/examples/README.md +9 -9
- package/harness-everything/examples/todo-workflow.md +22 -22
- package/harness-everything/scripts/todo-cli.js +157 -157
- package/harness-everything/scripts/verify-gate.js +31 -31
- package/hooks/scripts/atomic-commit-check.js +68 -68
- package/hooks/scripts/context-compact.js +114 -114
- package/hooks/scripts/lib/harness-state.js +29 -3
- package/hooks/scripts/lib/platforms/claude.js +42 -42
- package/hooks/scripts/lib/platforms/codex.js +46 -46
- package/hooks/scripts/lib/platforms/continue.js +41 -41
- package/hooks/scripts/lib/platforms/copilot.js +41 -41
- package/hooks/scripts/lib/platforms/cursor.js +41 -41
- package/hooks/scripts/rule-of-3.js +176 -176
- package/improve-codebase-architecture/SKILL.md +44 -44
- package/install-cognitive-os/SKILL.md +95 -95
- package/package.json +1 -1
- package/repo-docs/SKILL.md +45 -45
- package/rewrite-commits/SKILL.md +32 -32
- package/scripts/installer.js +649 -649
- package/scripts/lib/advisory-text.js +163 -163
- package/scripts/lib/claude-hooks.js +71 -71
- package/scripts/lib/gitignore.js +80 -58
- package/scripts/lib/manifest.js +82 -82
- package/scripts/lib/prompts.js +241 -241
- package/scripts/lib/skills.js +172 -172
- package/scripts/lib/workspace.js +28 -28
- package/security-review/SKILL.md +510 -510
- package/self-evolve/SKILL.md +54 -54
- package/skill-creator/SKILL.md +91 -91
- package/skill-creator/references/testing-workflow.md +32 -32
- package/skill-style/SKILL.md +48 -48
- package/tdd/SKILL.md +53 -53
- package/todo-driven-workflow/SKILL.md +54 -54
- package/using-git-worktrees/SKILL.md +161 -161
- package/verification-loop/SKILL.md +138 -138
- package/verify-before-claim/SKILL.md +86 -86
- package/zoom-out/SKILL.md +90 -90
|
@@ -1,163 +1,163 @@
|
|
|
1
|
-
// Prompt-injection-only guidance for platforms with no hook/execution
|
|
2
|
-
// mechanism (Cursor, Copilot, Codex, Continue, Hermes). Single source of
|
|
3
|
-
// truth for the advisory block - it used to be duplicated verbatim inside
|
|
4
|
-
// installer.js's top level and again inside main().
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
|
|
8
|
-
const MARKER = "Harness OS Guidance (Advisory)";
|
|
9
|
-
|
|
10
|
-
const advisoryInstructions = [
|
|
11
|
-
`\n# ${MARKER}`,
|
|
12
|
-
`This file is advisory only - this platform has no hook/execution mechanism to`,
|
|
13
|
-
`enforce it mechanically (unlike Claude Code's hook-based circuit breaker). Treat`,
|
|
14
|
-
`these as strong defaults, not guarantees.`,
|
|
15
|
-
``,
|
|
16
|
-
`## 🚦 MANDATORY ENTRY TRIAGE & ROUTING (ALWAYS RUN FIRST)`,
|
|
17
|
-
`Every time you receive a new prompt, you MUST load the \`harness-everything\` skill and immediately do the following:`,
|
|
18
|
-
`1. Run the Tier Router script: \`node harness-everything/scripts/tier-router.js "<Brief summary of user's prompt>"\` (or simulate its routing logic if terminal is not initialized yet).`,
|
|
19
|
-
`2. Output a clear, stylized routing checkpoint block at the VERY BEGINNING of your response to the user:`,
|
|
20
|
-
` \`\`\`markdown`,
|
|
21
|
-
` ## 🚦 Harness OS Routing Checkpoint`,
|
|
22
|
-
` - **Active Tier**: Tier 1 (Trivial) | Tier 2 (Standard) | Tier 3 (Macro)`,
|
|
23
|
-
` - **Rationale**: <1-sentence rationale from the tier router output>`,
|
|
24
|
-
` - **Routed Skills, Guides & Actions**:`,
|
|
25
|
-
` - \`path/to/skill/or/guide.md\` (<Brief reason why this guide/skill is loaded/used>)`,
|
|
26
|
-
` \`\`\``,
|
|
27
|
-
``,
|
|
28
|
-
`## 🤖 COGNITIVE COMPLIANCE (NO SILENT DEGRADES FOR NEW FEATURES)`,
|
|
29
|
-
`- **Newly Added Features / Extensions**: Copilot is highly prone to treating new feature requests as Tier 1 direct edits. If a task introduces *any* new logic, a new API endpoint, or a new file/module, you **MUST NOT** treat it as Tier 1. It **MUST** be treated as a **Tier 2 (Standard Task)** or **Tier 3 (Macro Task)**.`,
|
|
30
|
-
`- **Tier 2 Activation**: Initialize the \`todo-driven-workflow\` checklist first. Summon Domain Experts based on tech stack. Load and execute the \`tdd\` (Test-Driven Development) skill (write tests first, implement, refactor).`,
|
|
31
|
-
`- **Tier 3 Activation**: Initialize the \`todo-driven-workflow\` checklist, load \`fable-mode\` and \`fable-discipline\`, run sub-agents via \`create-agent-launcher\`, and write global docs using \`repo-docs\`.`,
|
|
32
|
-
`- **Memory Summarization (Self-Evolve)**: Upon task completion, you **MUST** run the \`self-evolve\` skill (running \`node self-evolve/scripts/self-regression.js\` or writing memories) to record key insights, lessons learned, and error boundaries so your context builds across sessions.`,
|
|
33
|
-
`- **Environment Discovery**: Discover the environment (OS, shell, package manager) before running commands - don't assume.`,
|
|
34
|
-
`- **Rule of 3**: If the same error repeats 3 times in a row, STOP retrying. Explain what's failing and ask the human for direction instead of continuing to guess.`,
|
|
35
|
-
`- **Prefer Editing**: Prefer editing over rewriting; commit logically complete chunks rather than one giant diff.`,
|
|
36
|
-
``
|
|
37
|
-
].join('\n');
|
|
38
|
-
|
|
39
|
-
function injectAdvisoryText(targetFile, header, label) {
|
|
40
|
-
const dir = path.dirname(targetFile);
|
|
41
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
42
|
-
|
|
43
|
-
if (fs.existsSync(targetFile)) {
|
|
44
|
-
const content = fs.readFileSync(targetFile, 'utf8');
|
|
45
|
-
if (!content.includes(MARKER)) {
|
|
46
|
-
fs.appendFileSync(targetFile, advisoryInstructions, 'utf8');
|
|
47
|
-
console.log(` ✅ Augmented existing ${label} with Harness guidance (advisory-only)`);
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
fs.writeFileSync(targetFile, `${header}\n${advisoryInstructions}`, 'utf8');
|
|
51
|
-
console.log(` ✅ Created ${label} with Harness guidance (advisory-only)`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function removeAdvisoryText(targetFile) {
|
|
56
|
-
if (!fs.existsSync(targetFile)) return;
|
|
57
|
-
try {
|
|
58
|
-
let content = fs.readFileSync(targetFile, 'utf8');
|
|
59
|
-
const markerIndex = content.indexOf(MARKER);
|
|
60
|
-
if (markerIndex !== -1) {
|
|
61
|
-
let cleanContent = content;
|
|
62
|
-
const hashMarkerIndex = content.lastIndexOf('#', markerIndex);
|
|
63
|
-
if (hashMarkerIndex !== -1) {
|
|
64
|
-
cleanContent = content.substring(0, hashMarkerIndex).trim() + '\n';
|
|
65
|
-
} else {
|
|
66
|
-
cleanContent = content.substring(0, markerIndex).trim() + '\n';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const lines = cleanContent.trim().split('\n').map(l => l.trim()).filter(l => l !== '');
|
|
70
|
-
if (lines.length === 0 || (lines.length === 1 && (lines[0] === '# Cursor Project Rules' || lines[0] === '# AGENTS.md' || lines[0] === '# Copilot Instructions' || lines[0] === '# .hermes.md'))) {
|
|
71
|
-
fs.unlinkSync(targetFile);
|
|
72
|
-
console.log(` ✅ Removed empty advisory file: ${targetFile}`);
|
|
73
|
-
} else {
|
|
74
|
-
fs.writeFileSync(targetFile, cleanContent.trim() + '\n', 'utf8');
|
|
75
|
-
console.log(` ✅ Removed Harness guidance from: ${targetFile}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} catch (e) {
|
|
79
|
-
console.warn(` ⚠️ Error removing advisory text from ${targetFile}: ${e.message}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Continue.dev reads project rules as individual Markdown files (with YAML
|
|
84
|
-
// frontmatter) from a `.continue/rules/` folder rather than one shared file,
|
|
85
|
-
// so - unlike the shared-file platforms above - Harness gets its own
|
|
86
|
-
// dedicated `harness.md` rule file instead of appending into an arbitrary
|
|
87
|
-
// pre-existing one.
|
|
88
|
-
function installContinueRule(targetFile, label) {
|
|
89
|
-
const dir = path.dirname(targetFile);
|
|
90
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
91
|
-
if (fs.existsSync(targetFile) && fs.readFileSync(targetFile, 'utf8').includes(MARKER)) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const content = [
|
|
95
|
-
`---`,
|
|
96
|
-
`name: Harness OS Guidance`,
|
|
97
|
-
`alwaysApply: true`,
|
|
98
|
-
`description: "Harness OS routing, circuit-breaker awareness, and environment-discovery guidance (advisory only on Continue - no hook mechanism)"`,
|
|
99
|
-
`---`,
|
|
100
|
-
advisoryInstructions.replace(/^\n/, '')
|
|
101
|
-
].join('\n');
|
|
102
|
-
fs.writeFileSync(targetFile, content, 'utf8');
|
|
103
|
-
console.log(` ✅ Installed Continue rule file with Harness guidance (advisory-only): ${label}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function removeContinueRule(targetFile) {
|
|
107
|
-
if (!fs.existsSync(targetFile)) return;
|
|
108
|
-
try {
|
|
109
|
-
const content = fs.readFileSync(targetFile, 'utf8');
|
|
110
|
-
if (content.includes(MARKER)) {
|
|
111
|
-
fs.unlinkSync(targetFile);
|
|
112
|
-
console.log(` ✅ Removed Harness rule file: ${targetFile}`);
|
|
113
|
-
}
|
|
114
|
-
} catch (e) {
|
|
115
|
-
console.warn(` ⚠️ Error removing ${targetFile}: ${e.message}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function buildCopilotGlobalContent() {
|
|
120
|
-
return [
|
|
121
|
-
`---`,
|
|
122
|
-
`description: "Harness OS Guidance (Advisory)"`,
|
|
123
|
-
`applyTo: "**"`,
|
|
124
|
-
`---`,
|
|
125
|
-
`# Copilot Instructions`,
|
|
126
|
-
``,
|
|
127
|
-
advisoryInstructions.replace(/^\n/, '')
|
|
128
|
-
].join('\n');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function buildCodexGlobalContent() {
|
|
132
|
-
return [
|
|
133
|
-
`---`,
|
|
134
|
-
`description: "Harness OS Guidance - Global custom agent for orchestrating multi-agent workflows"`,
|
|
135
|
-
`name: "Harness"`,
|
|
136
|
-
`user-invocable: true`,
|
|
137
|
-
`---`,
|
|
138
|
-
`# AGENTS.md`,
|
|
139
|
-
``,
|
|
140
|
-
`This file is advisory only - this platform has no hook/execution mechanism to`,
|
|
141
|
-
`enforce it mechanically (unlike Claude Code's hook-based circuit breaker). Treat`,
|
|
142
|
-
`these as strong defaults, not guarantees.`,
|
|
143
|
-
``,
|
|
144
|
-
`- Discover the environment (OS, shell, package manager) before running commands - don't assume.`,
|
|
145
|
-
`- Triage tasks: trivial fixes need no plan; standard features need tests; large refactors need`,
|
|
146
|
-
` an explicit plan reviewed with the human before implementation.`,
|
|
147
|
-
`- If the same error repeats 3 times in a row, STOP retrying. Explain what's failing and ask the`,
|
|
148
|
-
` human for direction instead of continuing to guess.`,
|
|
149
|
-
`- Prefer editing over rewriting; commit logically complete chunks rather than one giant diff.`,
|
|
150
|
-
``
|
|
151
|
-
].join('\n');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
module.exports = {
|
|
155
|
-
MARKER,
|
|
156
|
-
advisoryInstructions,
|
|
157
|
-
injectAdvisoryText,
|
|
158
|
-
removeAdvisoryText,
|
|
159
|
-
installContinueRule,
|
|
160
|
-
removeContinueRule,
|
|
161
|
-
buildCopilotGlobalContent,
|
|
162
|
-
buildCodexGlobalContent,
|
|
163
|
-
};
|
|
1
|
+
// Prompt-injection-only guidance for platforms with no hook/execution
|
|
2
|
+
// mechanism (Cursor, Copilot, Codex, Continue, Hermes). Single source of
|
|
3
|
+
// truth for the advisory block - it used to be duplicated verbatim inside
|
|
4
|
+
// installer.js's top level and again inside main().
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const MARKER = "Harness OS Guidance (Advisory)";
|
|
9
|
+
|
|
10
|
+
const advisoryInstructions = [
|
|
11
|
+
`\n# ${MARKER}`,
|
|
12
|
+
`This file is advisory only - this platform has no hook/execution mechanism to`,
|
|
13
|
+
`enforce it mechanically (unlike Claude Code's hook-based circuit breaker). Treat`,
|
|
14
|
+
`these as strong defaults, not guarantees.`,
|
|
15
|
+
``,
|
|
16
|
+
`## 🚦 MANDATORY ENTRY TRIAGE & ROUTING (ALWAYS RUN FIRST)`,
|
|
17
|
+
`Every time you receive a new prompt, you MUST load the \`harness-everything\` skill and immediately do the following:`,
|
|
18
|
+
`1. Run the Tier Router script: \`node harness-everything/scripts/tier-router.js "<Brief summary of user's prompt>"\` (or simulate its routing logic if terminal is not initialized yet).`,
|
|
19
|
+
`2. Output a clear, stylized routing checkpoint block at the VERY BEGINNING of your response to the user:`,
|
|
20
|
+
` \`\`\`markdown`,
|
|
21
|
+
` ## 🚦 Harness OS Routing Checkpoint`,
|
|
22
|
+
` - **Active Tier**: Tier 1 (Trivial) | Tier 2 (Standard) | Tier 3 (Macro)`,
|
|
23
|
+
` - **Rationale**: <1-sentence rationale from the tier router output>`,
|
|
24
|
+
` - **Routed Skills, Guides & Actions**:`,
|
|
25
|
+
` - \`path/to/skill/or/guide.md\` (<Brief reason why this guide/skill is loaded/used>)`,
|
|
26
|
+
` \`\`\``,
|
|
27
|
+
``,
|
|
28
|
+
`## 🤖 COGNITIVE COMPLIANCE (NO SILENT DEGRADES FOR NEW FEATURES)`,
|
|
29
|
+
`- **Newly Added Features / Extensions**: Copilot is highly prone to treating new feature requests as Tier 1 direct edits. If a task introduces *any* new logic, a new API endpoint, or a new file/module, you **MUST NOT** treat it as Tier 1. It **MUST** be treated as a **Tier 2 (Standard Task)** or **Tier 3 (Macro Task)**.`,
|
|
30
|
+
`- **Tier 2 Activation**: Initialize the \`todo-driven-workflow\` checklist first. Summon Domain Experts based on tech stack. Load and execute the \`tdd\` (Test-Driven Development) skill (write tests first, implement, refactor).`,
|
|
31
|
+
`- **Tier 3 Activation**: Initialize the \`todo-driven-workflow\` checklist, load \`fable-mode\` and \`fable-discipline\`, run sub-agents via \`create-agent-launcher\`, and write global docs using \`repo-docs\`.`,
|
|
32
|
+
`- **Memory Summarization (Self-Evolve)**: Upon task completion, you **MUST** run the \`self-evolve\` skill (running \`node self-evolve/scripts/self-regression.js\` or writing memories) to record key insights, lessons learned, and error boundaries so your context builds across sessions.`,
|
|
33
|
+
`- **Environment Discovery**: Discover the environment (OS, shell, package manager) before running commands - don't assume.`,
|
|
34
|
+
`- **Rule of 3**: If the same error repeats 3 times in a row, STOP retrying. Explain what's failing and ask the human for direction instead of continuing to guess.`,
|
|
35
|
+
`- **Prefer Editing**: Prefer editing over rewriting; commit logically complete chunks rather than one giant diff.`,
|
|
36
|
+
``
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
function injectAdvisoryText(targetFile, header, label) {
|
|
40
|
+
const dir = path.dirname(targetFile);
|
|
41
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
if (fs.existsSync(targetFile)) {
|
|
44
|
+
const content = fs.readFileSync(targetFile, 'utf8');
|
|
45
|
+
if (!content.includes(MARKER)) {
|
|
46
|
+
fs.appendFileSync(targetFile, advisoryInstructions, 'utf8');
|
|
47
|
+
console.log(` ✅ Augmented existing ${label} with Harness guidance (advisory-only)`);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
fs.writeFileSync(targetFile, `${header}\n${advisoryInstructions}`, 'utf8');
|
|
51
|
+
console.log(` ✅ Created ${label} with Harness guidance (advisory-only)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function removeAdvisoryText(targetFile) {
|
|
56
|
+
if (!fs.existsSync(targetFile)) return;
|
|
57
|
+
try {
|
|
58
|
+
let content = fs.readFileSync(targetFile, 'utf8');
|
|
59
|
+
const markerIndex = content.indexOf(MARKER);
|
|
60
|
+
if (markerIndex !== -1) {
|
|
61
|
+
let cleanContent = content;
|
|
62
|
+
const hashMarkerIndex = content.lastIndexOf('#', markerIndex);
|
|
63
|
+
if (hashMarkerIndex !== -1) {
|
|
64
|
+
cleanContent = content.substring(0, hashMarkerIndex).trim() + '\n';
|
|
65
|
+
} else {
|
|
66
|
+
cleanContent = content.substring(0, markerIndex).trim() + '\n';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const lines = cleanContent.trim().split('\n').map(l => l.trim()).filter(l => l !== '');
|
|
70
|
+
if (lines.length === 0 || (lines.length === 1 && (lines[0] === '# Cursor Project Rules' || lines[0] === '# AGENTS.md' || lines[0] === '# Copilot Instructions' || lines[0] === '# .hermes.md'))) {
|
|
71
|
+
fs.unlinkSync(targetFile);
|
|
72
|
+
console.log(` ✅ Removed empty advisory file: ${targetFile}`);
|
|
73
|
+
} else {
|
|
74
|
+
fs.writeFileSync(targetFile, cleanContent.trim() + '\n', 'utf8');
|
|
75
|
+
console.log(` ✅ Removed Harness guidance from: ${targetFile}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.warn(` ⚠️ Error removing advisory text from ${targetFile}: ${e.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Continue.dev reads project rules as individual Markdown files (with YAML
|
|
84
|
+
// frontmatter) from a `.continue/rules/` folder rather than one shared file,
|
|
85
|
+
// so - unlike the shared-file platforms above - Harness gets its own
|
|
86
|
+
// dedicated `harness.md` rule file instead of appending into an arbitrary
|
|
87
|
+
// pre-existing one.
|
|
88
|
+
function installContinueRule(targetFile, label) {
|
|
89
|
+
const dir = path.dirname(targetFile);
|
|
90
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
if (fs.existsSync(targetFile) && fs.readFileSync(targetFile, 'utf8').includes(MARKER)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const content = [
|
|
95
|
+
`---`,
|
|
96
|
+
`name: Harness OS Guidance`,
|
|
97
|
+
`alwaysApply: true`,
|
|
98
|
+
`description: "Harness OS routing, circuit-breaker awareness, and environment-discovery guidance (advisory only on Continue - no hook mechanism)"`,
|
|
99
|
+
`---`,
|
|
100
|
+
advisoryInstructions.replace(/^\n/, '')
|
|
101
|
+
].join('\n');
|
|
102
|
+
fs.writeFileSync(targetFile, content, 'utf8');
|
|
103
|
+
console.log(` ✅ Installed Continue rule file with Harness guidance (advisory-only): ${label}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function removeContinueRule(targetFile) {
|
|
107
|
+
if (!fs.existsSync(targetFile)) return;
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(targetFile, 'utf8');
|
|
110
|
+
if (content.includes(MARKER)) {
|
|
111
|
+
fs.unlinkSync(targetFile);
|
|
112
|
+
console.log(` ✅ Removed Harness rule file: ${targetFile}`);
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.warn(` ⚠️ Error removing ${targetFile}: ${e.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function buildCopilotGlobalContent() {
|
|
120
|
+
return [
|
|
121
|
+
`---`,
|
|
122
|
+
`description: "Harness OS Guidance (Advisory)"`,
|
|
123
|
+
`applyTo: "**"`,
|
|
124
|
+
`---`,
|
|
125
|
+
`# Copilot Instructions`,
|
|
126
|
+
``,
|
|
127
|
+
advisoryInstructions.replace(/^\n/, '')
|
|
128
|
+
].join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildCodexGlobalContent() {
|
|
132
|
+
return [
|
|
133
|
+
`---`,
|
|
134
|
+
`description: "Harness OS Guidance - Global custom agent for orchestrating multi-agent workflows"`,
|
|
135
|
+
`name: "Harness"`,
|
|
136
|
+
`user-invocable: true`,
|
|
137
|
+
`---`,
|
|
138
|
+
`# AGENTS.md`,
|
|
139
|
+
``,
|
|
140
|
+
`This file is advisory only - this platform has no hook/execution mechanism to`,
|
|
141
|
+
`enforce it mechanically (unlike Claude Code's hook-based circuit breaker). Treat`,
|
|
142
|
+
`these as strong defaults, not guarantees.`,
|
|
143
|
+
``,
|
|
144
|
+
`- Discover the environment (OS, shell, package manager) before running commands - don't assume.`,
|
|
145
|
+
`- Triage tasks: trivial fixes need no plan; standard features need tests; large refactors need`,
|
|
146
|
+
` an explicit plan reviewed with the human before implementation.`,
|
|
147
|
+
`- If the same error repeats 3 times in a row, STOP retrying. Explain what's failing and ask the`,
|
|
148
|
+
` human for direction instead of continuing to guess.`,
|
|
149
|
+
`- Prefer editing over rewriting; commit logically complete chunks rather than one giant diff.`,
|
|
150
|
+
``
|
|
151
|
+
].join('\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
MARKER,
|
|
156
|
+
advisoryInstructions,
|
|
157
|
+
injectAdvisoryText,
|
|
158
|
+
removeAdvisoryText,
|
|
159
|
+
installContinueRule,
|
|
160
|
+
removeContinueRule,
|
|
161
|
+
buildCopilotGlobalContent,
|
|
162
|
+
buildCodexGlobalContent,
|
|
163
|
+
};
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
// Merge/remove Harness's hook entries in a Claude Code settings.json without
|
|
2
|
-
// disturbing anything else a user (or another tool) put there.
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
|
|
5
|
-
// Every hook this package ships is namespaced "harness:<phase>:<name>" (see
|
|
6
|
-
// hooks/hooks.json) specifically so removal can match on that prefix alone -
|
|
7
|
-
// never on loose text like "does the command mention harness", which could
|
|
8
|
-
// false-positive on someone else's unrelated hook (e.g. a script whose path
|
|
9
|
-
// or name happens to contain "harness").
|
|
10
|
-
function isHarnessHook(hook) {
|
|
11
|
-
return !!(hook && hook.id && hook.id.startsWith('harness:'));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function mergeHarnessHooks(claudeConfig, resolvedHooks) {
|
|
15
|
-
claudeConfig.hooks = claudeConfig.hooks || {};
|
|
16
|
-
for (const [hookType, hookList] of Object.entries(resolvedHooks)) {
|
|
17
|
-
const existingList = claudeConfig.hooks[hookType] || [];
|
|
18
|
-
const mergedList = [...existingList];
|
|
19
|
-
hookList.forEach(newHook => {
|
|
20
|
-
const existsIdx = mergedList.findIndex(h => h.id === newHook.id);
|
|
21
|
-
if (existsIdx !== -1) {
|
|
22
|
-
mergedList[existsIdx] = newHook;
|
|
23
|
-
} else {
|
|
24
|
-
mergedList.push(newHook);
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
claudeConfig.hooks[hookType] = mergedList;
|
|
28
|
-
}
|
|
29
|
-
return claudeConfig;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function removeHarnessHooks(claudeSettingsFile) {
|
|
33
|
-
if (!fs.existsSync(claudeSettingsFile)) return false;
|
|
34
|
-
try {
|
|
35
|
-
const content = fs.readFileSync(claudeSettingsFile, 'utf8');
|
|
36
|
-
let config = JSON.parse(content);
|
|
37
|
-
if (!config.hooks) return false;
|
|
38
|
-
|
|
39
|
-
let modified = false;
|
|
40
|
-
for (const [hookType, hookList] of Object.entries(config.hooks)) {
|
|
41
|
-
if (!Array.isArray(hookList)) continue;
|
|
42
|
-
const originalLength = hookList.length;
|
|
43
|
-
config.hooks[hookType] = hookList.filter(hook => !isHarnessHook(hook));
|
|
44
|
-
if (config.hooks[hookType].length !== originalLength) {
|
|
45
|
-
modified = true;
|
|
46
|
-
}
|
|
47
|
-
if (config.hooks[hookType].length === 0) {
|
|
48
|
-
delete config.hooks[hookType];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (Object.keys(config.hooks || {}).length === 0) {
|
|
52
|
-
delete config.hooks;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (modified) {
|
|
56
|
-
if (Object.keys(config).length === 0) {
|
|
57
|
-
fs.unlinkSync(claudeSettingsFile);
|
|
58
|
-
console.log(` ✅ Cleaned up and removed empty Claude settings file: ${claudeSettingsFile}`);
|
|
59
|
-
} else {
|
|
60
|
-
fs.writeFileSync(claudeSettingsFile, JSON.stringify(config, null, 2), 'utf8');
|
|
61
|
-
console.log(` ✅ Safely removed Harness hooks from Claude settings: ${claudeSettingsFile}`);
|
|
62
|
-
}
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
} catch (e) {
|
|
66
|
-
console.warn(` ⚠️ Error cleaning Claude settings file ${claudeSettingsFile}: ${e.message}`);
|
|
67
|
-
}
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
module.exports = { isHarnessHook, mergeHarnessHooks, removeHarnessHooks };
|
|
1
|
+
// Merge/remove Harness's hook entries in a Claude Code settings.json without
|
|
2
|
+
// disturbing anything else a user (or another tool) put there.
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
// Every hook this package ships is namespaced "harness:<phase>:<name>" (see
|
|
6
|
+
// hooks/hooks.json) specifically so removal can match on that prefix alone -
|
|
7
|
+
// never on loose text like "does the command mention harness", which could
|
|
8
|
+
// false-positive on someone else's unrelated hook (e.g. a script whose path
|
|
9
|
+
// or name happens to contain "harness").
|
|
10
|
+
function isHarnessHook(hook) {
|
|
11
|
+
return !!(hook && hook.id && hook.id.startsWith('harness:'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function mergeHarnessHooks(claudeConfig, resolvedHooks) {
|
|
15
|
+
claudeConfig.hooks = claudeConfig.hooks || {};
|
|
16
|
+
for (const [hookType, hookList] of Object.entries(resolvedHooks)) {
|
|
17
|
+
const existingList = claudeConfig.hooks[hookType] || [];
|
|
18
|
+
const mergedList = [...existingList];
|
|
19
|
+
hookList.forEach(newHook => {
|
|
20
|
+
const existsIdx = mergedList.findIndex(h => h.id === newHook.id);
|
|
21
|
+
if (existsIdx !== -1) {
|
|
22
|
+
mergedList[existsIdx] = newHook;
|
|
23
|
+
} else {
|
|
24
|
+
mergedList.push(newHook);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
claudeConfig.hooks[hookType] = mergedList;
|
|
28
|
+
}
|
|
29
|
+
return claudeConfig;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function removeHarnessHooks(claudeSettingsFile) {
|
|
33
|
+
if (!fs.existsSync(claudeSettingsFile)) return false;
|
|
34
|
+
try {
|
|
35
|
+
const content = fs.readFileSync(claudeSettingsFile, 'utf8');
|
|
36
|
+
let config = JSON.parse(content);
|
|
37
|
+
if (!config.hooks) return false;
|
|
38
|
+
|
|
39
|
+
let modified = false;
|
|
40
|
+
for (const [hookType, hookList] of Object.entries(config.hooks)) {
|
|
41
|
+
if (!Array.isArray(hookList)) continue;
|
|
42
|
+
const originalLength = hookList.length;
|
|
43
|
+
config.hooks[hookType] = hookList.filter(hook => !isHarnessHook(hook));
|
|
44
|
+
if (config.hooks[hookType].length !== originalLength) {
|
|
45
|
+
modified = true;
|
|
46
|
+
}
|
|
47
|
+
if (config.hooks[hookType].length === 0) {
|
|
48
|
+
delete config.hooks[hookType];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (Object.keys(config.hooks || {}).length === 0) {
|
|
52
|
+
delete config.hooks;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (modified) {
|
|
56
|
+
if (Object.keys(config).length === 0) {
|
|
57
|
+
fs.unlinkSync(claudeSettingsFile);
|
|
58
|
+
console.log(` ✅ Cleaned up and removed empty Claude settings file: ${claudeSettingsFile}`);
|
|
59
|
+
} else {
|
|
60
|
+
fs.writeFileSync(claudeSettingsFile, JSON.stringify(config, null, 2), 'utf8');
|
|
61
|
+
console.log(` ✅ Safely removed Harness hooks from Claude settings: ${claudeSettingsFile}`);
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.warn(` ⚠️ Error cleaning Claude settings file ${claudeSettingsFile}: ${e.message}`);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { isHarnessHook, mergeHarnessHooks, removeHarnessHooks };
|
package/scripts/lib/gitignore.js
CHANGED
|
@@ -1,58 +1,80 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
function ensureWorkspaceGitignorePatterns(wsRoot, patterns) {
|
|
5
|
-
const gitignorePath = path.join(wsRoot, '.gitignore');
|
|
6
|
-
try {
|
|
7
|
-
let content = '';
|
|
8
|
-
if (fs.existsSync(gitignorePath)) {
|
|
9
|
-
content = fs.readFileSync(gitignorePath, 'utf8');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const lines = content.split(/\r?\n/);
|
|
13
|
-
const addedPatterns = [];
|
|
14
|
-
|
|
15
|
-
for (const pattern of patterns) {
|
|
16
|
-
const isIgnored = lines.some(line => {
|
|
17
|
-
const trimmed = line.trim();
|
|
18
|
-
return trimmed === pattern ||
|
|
19
|
-
trimmed === pattern.slice(0, -1) ||
|
|
20
|
-
(pattern === '.claude/harness-state/' && (trimmed === '.claude/' || trimmed === '.claude'));
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
if (!isIgnored) {
|
|
24
|
-
addedPatterns.push(pattern);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function ensureWorkspaceGitignorePatterns(wsRoot, patterns) {
|
|
5
|
+
const gitignorePath = path.join(wsRoot, '.gitignore');
|
|
6
|
+
try {
|
|
7
|
+
let content = '';
|
|
8
|
+
if (fs.existsSync(gitignorePath)) {
|
|
9
|
+
content = fs.readFileSync(gitignorePath, 'utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const lines = content.split(/\r?\n/);
|
|
13
|
+
const addedPatterns = [];
|
|
14
|
+
|
|
15
|
+
for (const pattern of patterns) {
|
|
16
|
+
const isIgnored = lines.some(line => {
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
return trimmed === pattern ||
|
|
19
|
+
trimmed === pattern.slice(0, -1) ||
|
|
20
|
+
(pattern === '.claude/harness-state/' && (trimmed === '.claude/' || trimmed === '.claude'));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!isIgnored) {
|
|
24
|
+
addedPatterns.push(pattern);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Same read-then-append shape as ensureHarnessStateIgnored() in
|
|
29
|
+
// hooks/scripts/lib/harness-state.js, which runs once per hook
|
|
30
|
+
// invocation and can race across concurrent processes to leave an
|
|
31
|
+
// exact-duplicate line behind - see the comment there. Collapsing
|
|
32
|
+
// duplicates here too keeps both writers self-healing the same file.
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
let sawDuplicate = false;
|
|
35
|
+
const dedupedLines = lines.filter(line => {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
if (trimmed === '' || trimmed.startsWith('#')) return true;
|
|
38
|
+
if (seen.has(trimmed)) {
|
|
39
|
+
sawDuplicate = true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
seen.add(trimmed);
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (addedPatterns.length > 0 || sawDuplicate) {
|
|
47
|
+
while (dedupedLines.length > 0 && dedupedLines[dedupedLines.length - 1] === '') {
|
|
48
|
+
dedupedLines.pop();
|
|
49
|
+
}
|
|
50
|
+
const finalLines = dedupedLines.concat(addedPatterns);
|
|
51
|
+
fs.writeFileSync(gitignorePath, finalLines.join('\n') + '\n', 'utf8');
|
|
52
|
+
if (addedPatterns.length > 0) {
|
|
53
|
+
console.log(` ✅ Added auto-generated directories to .gitignore: ${addedPatterns.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.warn(` ⚠️ Failed to update .gitignore: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Only ever deletes a directory that is genuinely empty, and never recurses
|
|
62
|
+
// past one of `stopDirs` (workspace root / user home / filesystem root) - so
|
|
63
|
+
// this can only ever tidy up directories Harness itself just emptied out,
|
|
64
|
+
// never reach into a parent that still holds unrelated content.
|
|
65
|
+
function cleanEmptyDirs(dir, stopDirs) {
|
|
66
|
+
if (!fs.existsSync(dir)) return;
|
|
67
|
+
if ((stopDirs || []).includes(dir) || dir === path.parse(dir).root) return;
|
|
68
|
+
try {
|
|
69
|
+
const files = fs.readdirSync(dir);
|
|
70
|
+
if (files.length === 0) {
|
|
71
|
+
fs.rmdirSync(dir);
|
|
72
|
+
console.log(` ✅ Cleaned up empty directory: ${dir}`);
|
|
73
|
+
cleanEmptyDirs(path.dirname(dir), stopDirs);
|
|
74
|
+
}
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// ignore
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { ensureWorkspaceGitignorePatterns, cleanEmptyDirs };
|