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.
Files changed (70) hide show
  1. package/README.md +1 -1
  2. package/build-multi-agent-system/SKILL.md +68 -68
  3. package/create-agent-launcher/SKILL.md +48 -48
  4. package/docs/architecture.md +2 -2
  5. package/docs/workflows/build-multi-agent-system.md +62 -62
  6. package/docs/workflows/create-agent-launcher.md +62 -62
  7. package/docs/workflows/environment-detection.md +63 -63
  8. package/docs/workflows/eval-harness.md +57 -57
  9. package/docs/workflows/fable-discipline.md +59 -59
  10. package/docs/workflows/fable-mode.md +32 -32
  11. package/docs/workflows/git-commit.md +62 -62
  12. package/docs/workflows/grill-me.md +62 -62
  13. package/docs/workflows/grill-with-docs.md +62 -62
  14. package/docs/workflows/improve-codebase-architecture.md +60 -60
  15. package/docs/workflows/repo-docs.md +59 -59
  16. package/docs/workflows/rewrite-commits.md +60 -60
  17. package/docs/workflows/security-review.md +58 -58
  18. package/docs/workflows/self-evolve.md +59 -59
  19. package/docs/workflows/tdd.md +39 -39
  20. package/docs/workflows/todo-driven-workflow.md +36 -36
  21. package/docs/workflows/using-git-worktrees.md +63 -63
  22. package/docs/workflows/verification-loop.md +69 -69
  23. package/docs/workflows/verify-before-claim.md +60 -60
  24. package/docs/workflows/zoom-out.md +62 -62
  25. package/environment-detection/SKILL.md +77 -77
  26. package/eval-framework/behavioral-test.js +105 -105
  27. package/eval-framework/mechanism-test.js +251 -251
  28. package/eval-harness/SKILL.md +59 -59
  29. package/fable-discipline/SKILL.md +38 -38
  30. package/fable-mode/SKILL.md +51 -51
  31. package/git-commit/SKILL.md +55 -55
  32. package/grill-me/SKILL.md +36 -36
  33. package/grill-with-docs/SKILL.md +99 -99
  34. package/harness-everything/SKILL.md +141 -141
  35. package/harness-everything/examples/README.md +9 -9
  36. package/harness-everything/examples/todo-workflow.md +22 -22
  37. package/harness-everything/scripts/todo-cli.js +157 -157
  38. package/harness-everything/scripts/verify-gate.js +31 -31
  39. package/hooks/scripts/atomic-commit-check.js +68 -68
  40. package/hooks/scripts/context-compact.js +114 -114
  41. package/hooks/scripts/lib/platforms/claude.js +132 -42
  42. package/hooks/scripts/lib/platforms/codex.js +96 -46
  43. package/hooks/scripts/lib/platforms/continue.js +73 -41
  44. package/hooks/scripts/lib/platforms/copilot.js +91 -41
  45. package/hooks/scripts/lib/platforms/cursor.js +71 -41
  46. package/hooks/scripts/lib/platforms/hermes.js +25 -0
  47. package/hooks/scripts/rule-of-3.js +176 -176
  48. package/improve-codebase-architecture/SKILL.md +44 -44
  49. package/install-cognitive-os/SKILL.md +95 -95
  50. package/package.json +1 -1
  51. package/repo-docs/SKILL.md +45 -45
  52. package/rewrite-commits/SKILL.md +32 -32
  53. package/scripts/installer.js +57 -200
  54. package/scripts/lib/advisory-text.js +163 -163
  55. package/scripts/lib/claude-hooks.js +71 -71
  56. package/scripts/lib/manifest.js +82 -82
  57. package/scripts/lib/prompts.js +241 -241
  58. package/scripts/lib/skills.js +174 -172
  59. package/scripts/lib/workspace.js +28 -28
  60. package/security-review/SKILL.md +510 -510
  61. package/self-evolve/SKILL.md +54 -54
  62. package/skill-creator/SKILL.md +91 -91
  63. package/skill-creator/references/testing-workflow.md +32 -32
  64. package/skill-style/SKILL.md +48 -48
  65. package/tdd/SKILL.md +53 -53
  66. package/todo-driven-workflow/SKILL.md +54 -54
  67. package/using-git-worktrees/SKILL.md +161 -161
  68. package/verification-loop/SKILL.md +138 -138
  69. package/verify-before-claim/SKILL.md +86 -86
  70. 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 no native project-skill directory this package can piggy
5
- // back on, so everything Harness adds locally - runtime state AND the
6
- // installed skill copies - lives under one self-contained subfolder of the
7
- // platform's own directory: .claude/harness-everything/. That keeps it out
8
- // of a self-invented top-level folder (the old .harness/) while still being
9
- // exclusively ours - nothing else creates a directory literally named
10
- // "harness-everything" inside .claude/, so it's trivial to add/remove
11
- // without touching anything else Claude Code or the user put in .claude/.
12
- module.exports = {
13
- name: 'claude',
14
- label: 'Claude Code',
15
- getHarnessDir(workspaceRoot) {
16
- return path.join(workspaceRoot, '.claude', 'harness-everything');
17
- },
18
- getStateDir(workspaceRoot) {
19
- return path.join(this.getHarnessDir(workspaceRoot), 'state');
20
- },
21
- getSkillsDir(workspaceRoot) {
22
- return path.join(this.getHarnessDir(workspaceRoot), 'skills');
23
- },
24
- getIgnorePatterns(workspaceRoot) {
25
- return ['.claude/harness-everything/'];
26
- },
27
- isMatch(pattern, trimmedLine) {
28
- if (pattern === '.claude/harness-everything/') {
29
- return trimmedLine === '.claude/' ||
30
- trimmedLine === '.claude' ||
31
- trimmedLine === '.claude/harness-everything' ||
32
- trimmedLine === '.claude/harness-everything/';
33
- }
34
- return trimmedLine === pattern || trimmedLine === pattern.slice(0, -1);
35
- },
36
- isInstalled(workspaceRoot, userHome, isGlobal) {
37
- if (isGlobal) {
38
- return fs.existsSync(path.join(userHome, '.claude', 'settings.json'));
39
- }
40
- return fs.existsSync(path.join(workspaceRoot, '.claude', 'settings.json'));
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
+ };