harness-everything 0.2.1 → 0.2.2
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/README.md +1 -1
- package/build-multi-agent-system/SKILL.md +68 -68
- package/create-agent-launcher/SKILL.md +48 -48
- package/docs/architecture.md +2 -2
- 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/platforms/claude.js +132 -42
- package/hooks/scripts/lib/platforms/codex.js +96 -46
- package/hooks/scripts/lib/platforms/continue.js +73 -41
- package/hooks/scripts/lib/platforms/copilot.js +91 -41
- package/hooks/scripts/lib/platforms/cursor.js +71 -41
- package/hooks/scripts/lib/platforms/hermes.js +25 -0
- 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 +57 -200
- package/scripts/lib/advisory-text.js +163 -163
- package/scripts/lib/claude-hooks.js +71 -71
- package/scripts/lib/manifest.js +82 -82
- package/scripts/lib/prompts.js +241 -241
- package/scripts/lib/skills.js +174 -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,114 +1,114 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const { execSync } = require('child_process');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { getWorkspaceRoot, getSessionDir } = require('./lib/harness-state');
|
|
6
|
-
|
|
7
|
-
function main(payload) {
|
|
8
|
-
try {
|
|
9
|
-
// Let's run a quick Git check to estimate context size from modified files
|
|
10
|
-
const statusRaw = execSync('git status --porcelain', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
11
|
-
if (!statusRaw) {
|
|
12
|
-
process.exit(0);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const lines = statusRaw.split('\n').filter(Boolean);
|
|
16
|
-
const modifiedCount = lines.length;
|
|
17
|
-
|
|
18
|
-
let totalDiffLines = 0;
|
|
19
|
-
try {
|
|
20
|
-
const diffStat = execSync('git diff --numstat', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
21
|
-
if (diffStat) {
|
|
22
|
-
diffStat.split('\n').forEach(line => {
|
|
23
|
-
const [added, deleted] = line.split('\t');
|
|
24
|
-
if (added && added !== '-' && deleted && deleted !== '-') {
|
|
25
|
-
totalDiffLines += parseInt(added) + parseInt(deleted);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
} catch (err) {
|
|
30
|
-
// Ignore git errors
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Thresholds for warning
|
|
34
|
-
const HIGH_FILE_LIMIT = 6;
|
|
35
|
-
const HIGH_LINE_LIMIT = 400;
|
|
36
|
-
|
|
37
|
-
const overThreshold = modifiedCount >= HIGH_FILE_LIMIT || totalDiffLines >= HIGH_LINE_LIMIT;
|
|
38
|
-
|
|
39
|
-
// Throttle: this hook fires on EVERY Bash/Edit/Write call, and a warning
|
|
40
|
-
// about context bloat that repeats on every call becomes context bloat
|
|
41
|
-
// itself (and trains the model to ignore hook output). Warn on the first
|
|
42
|
-
// crossing, then only every 10th call while still over - or when the diff
|
|
43
|
-
// has grown another 50% since the last warning.
|
|
44
|
-
const stateFile = path.join(getSessionDir(getWorkspaceRoot(), payload && payload.session_id), 'context-compact-state.json');
|
|
45
|
-
let state = { overCount: 0, lastWarnedLines: 0 };
|
|
46
|
-
try {
|
|
47
|
-
if (fs.existsSync(stateFile)) state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
48
|
-
} catch (err) { /* use default */ }
|
|
49
|
-
|
|
50
|
-
if (!overThreshold) {
|
|
51
|
-
if (state.overCount > 0) {
|
|
52
|
-
try {
|
|
53
|
-
fs.writeFileSync(stateFile, JSON.stringify({ overCount: 0, lastWarnedLines: 0 }), 'utf8');
|
|
54
|
-
} catch (err) { /* ignore */ }
|
|
55
|
-
}
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
state.overCount += 1;
|
|
60
|
-
const onBeat = state.overCount === 1 || state.overCount % 10 === 0;
|
|
61
|
-
const grewALot = totalDiffLines >= (state.lastWarnedLines || 0) * 1.5 && totalDiffLines > state.lastWarnedLines;
|
|
62
|
-
const shouldWarn = onBeat || grewALot;
|
|
63
|
-
if (shouldWarn) state.lastWarnedLines = totalDiffLines;
|
|
64
|
-
try {
|
|
65
|
-
fs.writeFileSync(stateFile, JSON.stringify(state), 'utf8');
|
|
66
|
-
} catch (err) { /* ignore */ }
|
|
67
|
-
|
|
68
|
-
if (shouldWarn) {
|
|
69
|
-
// Plain stdout on exit 0 is only surfaced to Claude for UserPromptSubmit/
|
|
70
|
-
// SessionStart - for PreToolUse it's written to a debug log Claude never
|
|
71
|
-
// sees. To actually get an advisory (non-blocking) message in front of
|
|
72
|
-
// Claude here, it has to go through hookSpecificOutput.additionalContext.
|
|
73
|
-
const message = [
|
|
74
|
-
`Harness OS - Context Bloat Warning`,
|
|
75
|
-
`- Modified Files: ${modifiedCount}`,
|
|
76
|
-
`- Accumulated Diff: ${totalDiffLines} lines`,
|
|
77
|
-
``,
|
|
78
|
-
`Caution: Large number of modified files or big diffs will bloat the Agent context window, leading to:`,
|
|
79
|
-
` 1. "Lost in the Middle" reasoning degradation`,
|
|
80
|
-
` 2. Rapidly inflating token costs`,
|
|
81
|
-
``,
|
|
82
|
-
`Recommended Action:`,
|
|
83
|
-
` - Run atomic commits or stage completed files.`,
|
|
84
|
-
` - Use targeted sub-folders rather than broad searches.`,
|
|
85
|
-
` - Ask user to run '/compact' or '/clear' if supported.`
|
|
86
|
-
].join('\n');
|
|
87
|
-
|
|
88
|
-
console.log(JSON.stringify({
|
|
89
|
-
hookSpecificOutput: {
|
|
90
|
-
hookEventName: "PreToolUse",
|
|
91
|
-
additionalContext: message
|
|
92
|
-
}
|
|
93
|
-
}));
|
|
94
|
-
}
|
|
95
|
-
} catch (err) {
|
|
96
|
-
// Fail open - never block user execution on compaction checker
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
process.exit(0);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let inputData = '';
|
|
103
|
-
const timeout = setTimeout(() => { main(null); }, 200);
|
|
104
|
-
process.stdin.on('data', chunk => { inputData += chunk; });
|
|
105
|
-
process.stdin.on('end', () => {
|
|
106
|
-
clearTimeout(timeout);
|
|
107
|
-
let payload = null;
|
|
108
|
-
try { payload = JSON.parse(inputData.trim()); } catch (err) { /* no payload */ }
|
|
109
|
-
main(payload);
|
|
110
|
-
});
|
|
111
|
-
process.stdin.on('error', () => {
|
|
112
|
-
clearTimeout(timeout);
|
|
113
|
-
main(null);
|
|
114
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { getWorkspaceRoot, getSessionDir } = require('./lib/harness-state');
|
|
6
|
+
|
|
7
|
+
function main(payload) {
|
|
8
|
+
try {
|
|
9
|
+
// Let's run a quick Git check to estimate context size from modified files
|
|
10
|
+
const statusRaw = execSync('git status --porcelain', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
11
|
+
if (!statusRaw) {
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const lines = statusRaw.split('\n').filter(Boolean);
|
|
16
|
+
const modifiedCount = lines.length;
|
|
17
|
+
|
|
18
|
+
let totalDiffLines = 0;
|
|
19
|
+
try {
|
|
20
|
+
const diffStat = execSync('git diff --numstat', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
21
|
+
if (diffStat) {
|
|
22
|
+
diffStat.split('\n').forEach(line => {
|
|
23
|
+
const [added, deleted] = line.split('\t');
|
|
24
|
+
if (added && added !== '-' && deleted && deleted !== '-') {
|
|
25
|
+
totalDiffLines += parseInt(added) + parseInt(deleted);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
// Ignore git errors
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Thresholds for warning
|
|
34
|
+
const HIGH_FILE_LIMIT = 6;
|
|
35
|
+
const HIGH_LINE_LIMIT = 400;
|
|
36
|
+
|
|
37
|
+
const overThreshold = modifiedCount >= HIGH_FILE_LIMIT || totalDiffLines >= HIGH_LINE_LIMIT;
|
|
38
|
+
|
|
39
|
+
// Throttle: this hook fires on EVERY Bash/Edit/Write call, and a warning
|
|
40
|
+
// about context bloat that repeats on every call becomes context bloat
|
|
41
|
+
// itself (and trains the model to ignore hook output). Warn on the first
|
|
42
|
+
// crossing, then only every 10th call while still over - or when the diff
|
|
43
|
+
// has grown another 50% since the last warning.
|
|
44
|
+
const stateFile = path.join(getSessionDir(getWorkspaceRoot(), payload && payload.session_id), 'context-compact-state.json');
|
|
45
|
+
let state = { overCount: 0, lastWarnedLines: 0 };
|
|
46
|
+
try {
|
|
47
|
+
if (fs.existsSync(stateFile)) state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
48
|
+
} catch (err) { /* use default */ }
|
|
49
|
+
|
|
50
|
+
if (!overThreshold) {
|
|
51
|
+
if (state.overCount > 0) {
|
|
52
|
+
try {
|
|
53
|
+
fs.writeFileSync(stateFile, JSON.stringify({ overCount: 0, lastWarnedLines: 0 }), 'utf8');
|
|
54
|
+
} catch (err) { /* ignore */ }
|
|
55
|
+
}
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
state.overCount += 1;
|
|
60
|
+
const onBeat = state.overCount === 1 || state.overCount % 10 === 0;
|
|
61
|
+
const grewALot = totalDiffLines >= (state.lastWarnedLines || 0) * 1.5 && totalDiffLines > state.lastWarnedLines;
|
|
62
|
+
const shouldWarn = onBeat || grewALot;
|
|
63
|
+
if (shouldWarn) state.lastWarnedLines = totalDiffLines;
|
|
64
|
+
try {
|
|
65
|
+
fs.writeFileSync(stateFile, JSON.stringify(state), 'utf8');
|
|
66
|
+
} catch (err) { /* ignore */ }
|
|
67
|
+
|
|
68
|
+
if (shouldWarn) {
|
|
69
|
+
// Plain stdout on exit 0 is only surfaced to Claude for UserPromptSubmit/
|
|
70
|
+
// SessionStart - for PreToolUse it's written to a debug log Claude never
|
|
71
|
+
// sees. To actually get an advisory (non-blocking) message in front of
|
|
72
|
+
// Claude here, it has to go through hookSpecificOutput.additionalContext.
|
|
73
|
+
const message = [
|
|
74
|
+
`Harness OS - Context Bloat Warning`,
|
|
75
|
+
`- Modified Files: ${modifiedCount}`,
|
|
76
|
+
`- Accumulated Diff: ${totalDiffLines} lines`,
|
|
77
|
+
``,
|
|
78
|
+
`Caution: Large number of modified files or big diffs will bloat the Agent context window, leading to:`,
|
|
79
|
+
` 1. "Lost in the Middle" reasoning degradation`,
|
|
80
|
+
` 2. Rapidly inflating token costs`,
|
|
81
|
+
``,
|
|
82
|
+
`Recommended Action:`,
|
|
83
|
+
` - Run atomic commits or stage completed files.`,
|
|
84
|
+
` - Use targeted sub-folders rather than broad searches.`,
|
|
85
|
+
` - Ask user to run '/compact' or '/clear' if supported.`
|
|
86
|
+
].join('\n');
|
|
87
|
+
|
|
88
|
+
console.log(JSON.stringify({
|
|
89
|
+
hookSpecificOutput: {
|
|
90
|
+
hookEventName: "PreToolUse",
|
|
91
|
+
additionalContext: message
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
} catch (err) {
|
|
96
|
+
// Fail open - never block user execution on compaction checker
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let inputData = '';
|
|
103
|
+
const timeout = setTimeout(() => { main(null); }, 200);
|
|
104
|
+
process.stdin.on('data', chunk => { inputData += chunk; });
|
|
105
|
+
process.stdin.on('end', () => {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
let payload = null;
|
|
108
|
+
try { payload = JSON.parse(inputData.trim()); } catch (err) { /* no payload */ }
|
|
109
|
+
main(payload);
|
|
110
|
+
});
|
|
111
|
+
process.stdin.on('error', () => {
|
|
112
|
+
clearTimeout(timeout);
|
|
113
|
+
main(null);
|
|
114
|
+
});
|
|
@@ -1,42 +1,132 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
|
|
4
|
-
// Claude Code has
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return path.join(workspaceRoot, '.claude', '
|
|
17
|
-
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
trimmedLine === '.claude'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
// Claude Code has native project-skill directory: .claude/skills/.
|
|
5
|
+
// Runtime state converges into .claude/harness-everything/.
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: 'claude',
|
|
8
|
+
label: 'Claude Code',
|
|
9
|
+
getHarnessDir(workspaceRoot) {
|
|
10
|
+
return path.join(workspaceRoot, '.claude', 'harness-everything');
|
|
11
|
+
},
|
|
12
|
+
getStateDir(workspaceRoot) {
|
|
13
|
+
return path.join(this.getHarnessDir(workspaceRoot), 'state');
|
|
14
|
+
},
|
|
15
|
+
getSkillsDir(workspaceRoot) {
|
|
16
|
+
return path.join(workspaceRoot, '.claude', 'skills');
|
|
17
|
+
},
|
|
18
|
+
getIgnorePatterns(workspaceRoot) {
|
|
19
|
+
const patterns = ['.claude/harness-everything/'];
|
|
20
|
+
if (fs.existsSync(path.join(workspaceRoot, '.claude', 'skills'))) {
|
|
21
|
+
patterns.push('.claude/skills/');
|
|
22
|
+
}
|
|
23
|
+
return patterns;
|
|
24
|
+
},
|
|
25
|
+
isMatch(pattern, trimmedLine) {
|
|
26
|
+
if (pattern === '.claude/harness-everything/') {
|
|
27
|
+
return trimmedLine === '.claude/' ||
|
|
28
|
+
trimmedLine === '.claude' ||
|
|
29
|
+
trimmedLine === '.claude/harness-everything' ||
|
|
30
|
+
trimmedLine === '.claude/harness-everything/';
|
|
31
|
+
}
|
|
32
|
+
return trimmedLine === pattern || trimmedLine === pattern.slice(0, -1);
|
|
33
|
+
},
|
|
34
|
+
isInstalled(workspaceRoot, userHome, isGlobal) {
|
|
35
|
+
if (isGlobal) {
|
|
36
|
+
return fs.existsSync(path.join(userHome, '.claude', 'settings.json'));
|
|
37
|
+
}
|
|
38
|
+
return fs.existsSync(path.join(workspaceRoot, '.claude', 'settings.json'));
|
|
39
|
+
},
|
|
40
|
+
getSkillsTarget({ workspaceRoot, userHome, isGlobal, manifest }) {
|
|
41
|
+
if (isGlobal) {
|
|
42
|
+
const claudeDir = path.join(userHome, '.claude');
|
|
43
|
+
return {
|
|
44
|
+
path: path.join(claudeDir, 'skills'),
|
|
45
|
+
label: '~/.claude/skills/',
|
|
46
|
+
manifestPath: manifest.getManifestPath(claudeDir)
|
|
47
|
+
};
|
|
48
|
+
} else {
|
|
49
|
+
const claudeDir = path.join(workspaceRoot, '.claude');
|
|
50
|
+
return {
|
|
51
|
+
path: path.join(claudeDir, 'skills'),
|
|
52
|
+
label: '.claude/skills/',
|
|
53
|
+
manifestPath: manifest.getManifestPath(claudeDir)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
install({ isGlobal, targetWorkspaceRoot, harnessSourceDir, packageVersion, getUserPromptsDir, advisory, claudeHooks, manifest }) {
|
|
58
|
+
const userHome = require('os').homedir();
|
|
59
|
+
const claudeDir = isGlobal ? path.join(userHome, '.claude') : path.join(targetWorkspaceRoot, '.claude');
|
|
60
|
+
if (!fs.existsSync(claudeDir)) {
|
|
61
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
62
|
+
console.log(` Created ${isGlobal ? '~/.claude' : '.claude'} directory`);
|
|
63
|
+
}
|
|
64
|
+
const claudeSettingsFile = path.join(claudeDir, 'settings.json');
|
|
65
|
+
let claudeConfig = {};
|
|
66
|
+
if (fs.existsSync(claudeSettingsFile)) {
|
|
67
|
+
try {
|
|
68
|
+
claudeConfig = JSON.parse(fs.readFileSync(claudeSettingsFile, 'utf8'));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.warn(` ⚠️ Existing ${isGlobal ? '~' : ''}/.claude/settings.json is malformed, creating fresh one.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sourceHooksFile = path.join(harnessSourceDir, 'hooks', 'hooks.json');
|
|
75
|
+
if (fs.existsSync(sourceHooksFile)) {
|
|
76
|
+
const sourceHooksObj = JSON.parse(fs.readFileSync(sourceHooksFile, 'utf8'));
|
|
77
|
+
const resolvedHooks = {};
|
|
78
|
+
for (const [hookType, hookList] of Object.entries(sourceHooksObj.hooks || {})) {
|
|
79
|
+
resolvedHooks[hookType] = hookList.map(hookItem => {
|
|
80
|
+
const cloned = JSON.parse(JSON.stringify(hookItem));
|
|
81
|
+
if (cloned.hooks) {
|
|
82
|
+
cloned.hooks = cloned.hooks.map(h => {
|
|
83
|
+
if (h.type === 'command' && h.command) {
|
|
84
|
+
h.command = h.command.replace(/^node\s+"?([^"\s]+)"?/, (m, scriptPath) => {
|
|
85
|
+
const abs = path.isAbsolute(scriptPath) ? scriptPath : path.join(harnessSourceDir, scriptPath);
|
|
86
|
+
return `node "${abs}"`;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return h;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
cloned.harness = { package: manifest.PACKAGE_NAME, version: packageVersion, author: manifest.HARNESS_AUTHOR };
|
|
93
|
+
return cloned;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
claudeHooks.mergeHarnessHooks(claudeConfig, resolvedHooks);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(claudeSettingsFile, JSON.stringify(claudeConfig, null, 2), 'utf8');
|
|
100
|
+
console.log(` ✅ Configured Claude Code hooks safely in ${isGlobal ? '~' : ''}/.claude/settings.json`);
|
|
101
|
+
|
|
102
|
+
// DOWNWARD COMPATIBLE CLEANUP: Remove old wrong skill folder .claude/harness-everything/skills if it exists
|
|
103
|
+
const oldSkillsDir = path.join(claudeDir, 'harness-everything', 'skills');
|
|
104
|
+
if (fs.existsSync(oldSkillsDir)) {
|
|
105
|
+
try {
|
|
106
|
+
fs.rmSync(oldSkillsDir, { recursive: true, force: true });
|
|
107
|
+
console.log(` 🧹 Cleaned up legacy incorrect local skills folder at: .claude/harness-everything/skills/`);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
console.warn(` ⚠️ Failed to remove legacy skills folder: ${e.message}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
uninstall({ removeLocal, removeGlobal, workspaceRoot, userHome, getUserPromptsDir, advisory, claudeHooks, manifest, cleanEmptyDirs }) {
|
|
114
|
+
if (removeLocal) {
|
|
115
|
+
const localSettingsFile = path.join(workspaceRoot, '.claude', 'settings.json');
|
|
116
|
+
claudeHooks.removeHarnessHooks(localSettingsFile);
|
|
117
|
+
|
|
118
|
+
// Clear old wrong skill folder .claude/harness-everything/skills if any remain
|
|
119
|
+
const oldSkillsDir = path.join(workspaceRoot, '.claude', 'harness-everything', 'skills');
|
|
120
|
+
if (fs.existsSync(oldSkillsDir)) {
|
|
121
|
+
fs.rmSync(oldSkillsDir, { recursive: true, force: true });
|
|
122
|
+
console.log(` 🧹 Cleaned up legacy incorrect local skills folder at: .claude/harness-everything/skills/`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (removeGlobal) {
|
|
126
|
+
const globalSettingsFile = path.join(userHome, '.claude', 'settings.json');
|
|
127
|
+
claudeHooks.removeHarnessHooks(globalSettingsFile);
|
|
128
|
+
const globalClaudeDir = path.join(userHome, '.claude');
|
|
129
|
+
cleanEmptyDirs(globalClaudeDir, [userHome]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
@@ -1,46 +1,96 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
|
|
4
|
-
// Codex CLI's real project-scoped home is `.codex/` (it reads `.codex/skills/`
|
|
5
|
-
// for project skills and `.codex/config.toml` for CLI/sandbox config, walking
|
|
6
|
-
// up to the `.git` root exactly like AGENTS.md - see
|
|
7
|
-
// https://developers.openai.com/codex/config-advanced and
|
|
8
|
-
// https://www.agensi.io/learn/codex-cli-agents-md-complete-guide). Skills
|
|
9
|
-
// stay at that native `.codex/skills/` location; only bookkeeping that never
|
|
10
|
-
// had a home (runtime state, install manifest) converges into
|
|
11
|
-
// `.codex/harness-everything/`.
|
|
12
|
-
module.exports = {
|
|
13
|
-
name: 'codex',
|
|
14
|
-
label: 'Codex / Gemini',
|
|
15
|
-
getHarnessDir(workspaceRoot) {
|
|
16
|
-
return path.join(workspaceRoot, '.codex', 'harness-everything');
|
|
17
|
-
},
|
|
18
|
-
getStateDir(workspaceRoot) {
|
|
19
|
-
return path.join(this.getHarnessDir(workspaceRoot), 'state');
|
|
20
|
-
},
|
|
21
|
-
getSkillsDir(workspaceRoot) {
|
|
22
|
-
return path.join(workspaceRoot, '.codex', 'skills');
|
|
23
|
-
},
|
|
24
|
-
getIgnorePatterns(workspaceRoot) {
|
|
25
|
-
const patterns = ['.codex/harness-everything/'];
|
|
26
|
-
if (fs.existsSync(path.join(workspaceRoot, '.codex', 'skills'))) {
|
|
27
|
-
patterns.push('.codex/skills/');
|
|
28
|
-
}
|
|
29
|
-
return patterns;
|
|
30
|
-
},
|
|
31
|
-
isMatch(pattern, trimmedLine) {
|
|
32
|
-
if (pattern === '.codex/harness-everything/') {
|
|
33
|
-
return trimmedLine === '.codex/' ||
|
|
34
|
-
trimmedLine === '.codex' ||
|
|
35
|
-
trimmedLine === '.codex/harness-everything' ||
|
|
36
|
-
trimmedLine === '.codex/harness-everything/';
|
|
37
|
-
}
|
|
38
|
-
return trimmedLine === pattern || trimmedLine === pattern.slice(0, -1);
|
|
39
|
-
},
|
|
40
|
-
isInstalled(workspaceRoot, userHome, isGlobal) {
|
|
41
|
-
if (isGlobal) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
return fs.existsSync(path.join(workspaceRoot, 'AGENTS.md'));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
// Codex CLI's real project-scoped home is `.codex/` (it reads `.codex/skills/`
|
|
5
|
+
// for project skills and `.codex/config.toml` for CLI/sandbox config, walking
|
|
6
|
+
// up to the `.git` root exactly like AGENTS.md - see
|
|
7
|
+
// https://developers.openai.com/codex/config-advanced and
|
|
8
|
+
// https://www.agensi.io/learn/codex-cli-agents-md-complete-guide). Skills
|
|
9
|
+
// stay at that native `.codex/skills/` location; only bookkeeping that never
|
|
10
|
+
// had a home (runtime state, install manifest) converges into
|
|
11
|
+
// `.codex/harness-everything/`.
|
|
12
|
+
module.exports = {
|
|
13
|
+
name: 'codex',
|
|
14
|
+
label: 'Codex / Gemini',
|
|
15
|
+
getHarnessDir(workspaceRoot) {
|
|
16
|
+
return path.join(workspaceRoot, '.codex', 'harness-everything');
|
|
17
|
+
},
|
|
18
|
+
getStateDir(workspaceRoot) {
|
|
19
|
+
return path.join(this.getHarnessDir(workspaceRoot), 'state');
|
|
20
|
+
},
|
|
21
|
+
getSkillsDir(workspaceRoot) {
|
|
22
|
+
return path.join(workspaceRoot, '.codex', 'skills');
|
|
23
|
+
},
|
|
24
|
+
getIgnorePatterns(workspaceRoot) {
|
|
25
|
+
const patterns = ['.codex/harness-everything/'];
|
|
26
|
+
if (fs.existsSync(path.join(workspaceRoot, '.codex', 'skills'))) {
|
|
27
|
+
patterns.push('.codex/skills/');
|
|
28
|
+
}
|
|
29
|
+
return patterns;
|
|
30
|
+
},
|
|
31
|
+
isMatch(pattern, trimmedLine) {
|
|
32
|
+
if (pattern === '.codex/harness-everything/') {
|
|
33
|
+
return trimmedLine === '.codex/' ||
|
|
34
|
+
trimmedLine === '.codex' ||
|
|
35
|
+
trimmedLine === '.codex/harness-everything' ||
|
|
36
|
+
trimmedLine === '.codex/harness-everything/';
|
|
37
|
+
}
|
|
38
|
+
return trimmedLine === pattern || trimmedLine === pattern.slice(0, -1);
|
|
39
|
+
},
|
|
40
|
+
isInstalled(workspaceRoot, userHome, isGlobal) {
|
|
41
|
+
if (isGlobal) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return fs.existsSync(path.join(workspaceRoot, 'AGENTS.md'));
|
|
45
|
+
},
|
|
46
|
+
getSkillsTarget({ workspaceRoot, userHome, isGlobal, manifest }) {
|
|
47
|
+
if (isGlobal) {
|
|
48
|
+
const globalAgentsDir = path.join(userHome, '.agents');
|
|
49
|
+
return {
|
|
50
|
+
path: path.join(globalAgentsDir, 'skills'),
|
|
51
|
+
label: '~/.agents/skills/',
|
|
52
|
+
manifestPath: manifest.getManifestPath(globalAgentsDir),
|
|
53
|
+
};
|
|
54
|
+
} else {
|
|
55
|
+
const codexDir = path.join(workspaceRoot, '.codex');
|
|
56
|
+
return {
|
|
57
|
+
path: path.join(codexDir, 'skills'),
|
|
58
|
+
label: '.codex/skills/',
|
|
59
|
+
manifestPath: manifest.getManifestPath(codexDir),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
install({ isGlobal, targetWorkspaceRoot, getUserPromptsDir, advisory }) {
|
|
64
|
+
if (!isGlobal) {
|
|
65
|
+
const targetFile = path.join(targetWorkspaceRoot, 'AGENTS.md');
|
|
66
|
+
advisory.injectAdvisoryText(targetFile, '# AGENTS.md', 'AGENTS.md');
|
|
67
|
+
} else {
|
|
68
|
+
try {
|
|
69
|
+
const promptsDir = getUserPromptsDir();
|
|
70
|
+
if (!fs.existsSync(promptsDir)) {
|
|
71
|
+
fs.mkdirSync(promptsDir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
const vscodeAgentFile = path.join(promptsDir, 'harness.agent.md');
|
|
74
|
+
fs.writeFileSync(vscodeAgentFile, advisory.buildCodexGlobalContent(), 'utf8');
|
|
75
|
+
console.log(` ✅ Installed global Codex agent to VS Code: ${vscodeAgentFile}`);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.warn(` ⚠️ Failed to write VS Code user prompts folder: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
uninstall({ removeLocal, removeGlobal, workspaceRoot, userHome, getUserPromptsDir, cleanEmptyDirs }) {
|
|
82
|
+
const advisory = require('../../../../scripts/lib/advisory-text');
|
|
83
|
+
if (removeLocal) {
|
|
84
|
+
advisory.removeAdvisoryText(path.join(workspaceRoot, 'AGENTS.md'));
|
|
85
|
+
}
|
|
86
|
+
if (removeGlobal) {
|
|
87
|
+
const promptsDir = getUserPromptsDir();
|
|
88
|
+
const vscodeAgentFile = path.join(promptsDir, 'harness.agent.md');
|
|
89
|
+
if (fs.existsSync(vscodeAgentFile)) {
|
|
90
|
+
fs.unlinkSync(vscodeAgentFile);
|
|
91
|
+
console.log(` ✅ Removed global Codex agent: ${vscodeAgentFile}`);
|
|
92
|
+
}
|
|
93
|
+
cleanEmptyDirs(promptsDir, [userHome]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|