harness-everything 0.2.0 → 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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/docs/architecture.md +2 -2
- package/hooks/scripts/lib/harness-state.js +29 -3
- package/hooks/scripts/lib/platforms/claude.js +100 -10
- package/hooks/scripts/lib/platforms/codex.js +50 -0
- package/hooks/scripts/lib/platforms/continue.js +32 -0
- package/hooks/scripts/lib/platforms/copilot.js +50 -0
- package/hooks/scripts/lib/platforms/cursor.js +30 -0
- package/hooks/scripts/lib/platforms/hermes.js +25 -0
- package/package.json +1 -1
- package/scripts/installer.js +506 -649
- package/scripts/lib/gitignore.js +80 -58
- package/scripts/lib/skills.js +2 -0
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 };
|
package/scripts/lib/skills.js
CHANGED
|
@@ -110,6 +110,7 @@ function getInstalledSkills(workspaceRoot, userHome) {
|
|
|
110
110
|
{ home: path.join(workspaceRoot, '.github'), scope: 'local (Copilot)' },
|
|
111
111
|
{ home: path.join(workspaceRoot, '.codex'), scope: 'local (Codex)' },
|
|
112
112
|
{ home: path.join(workspaceRoot, '.continue'), scope: 'local (Continue)' },
|
|
113
|
+
{ home: path.join(userHome, '.claude'), scope: 'global (Claude)' },
|
|
113
114
|
{ home: globalAgentsDir, scope: 'global' },
|
|
114
115
|
];
|
|
115
116
|
for (const { home, scope } of manifestHomes) {
|
|
@@ -124,6 +125,7 @@ function getInstalledSkills(workspaceRoot, userHome) {
|
|
|
124
125
|
// author marker - never a blind directory listing.
|
|
125
126
|
const legacyDirs = [
|
|
126
127
|
{ path: path.join(workspaceRoot, '.harness', 'skills'), scope: 'local (legacy .harness)', manifestPath: manifest.getManifestPath(path.join(workspaceRoot, '.claude')) },
|
|
128
|
+
{ path: path.join(workspaceRoot, '.claude', 'harness-everything', 'skills'), scope: 'local (Claude legacy erroneous path)', manifestPath: manifest.getManifestPath(path.join(workspaceRoot, '.claude')) },
|
|
127
129
|
{ path: path.join(workspaceRoot, '.cursor', 'skills'), scope: 'local (Cursor)', manifestPath: manifest.getManifestPath(path.join(workspaceRoot, '.cursor')) },
|
|
128
130
|
{ path: path.join(workspaceRoot, '.github', 'skills'), scope: 'local (Copilot)', manifestPath: manifest.getManifestPath(path.join(workspaceRoot, '.github')) },
|
|
129
131
|
{ path: path.join(workspaceRoot, '.codex', 'skills'), scope: 'local (Codex)', manifestPath: manifest.getManifestPath(path.join(workspaceRoot, '.codex')) },
|