harness-everything 0.2.0
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/AGENTS.md +2 -0
- package/BENCHMARK_SOP.md +62 -0
- package/CHANGELOG.md +107 -0
- package/Dockerfile +18 -0
- package/LICENSE +201 -0
- package/README.md +197 -0
- package/VERIFICATION.md +347 -0
- package/bin/cli.js +97 -0
- package/build-multi-agent-system/AGENTS.md +50 -0
- package/build-multi-agent-system/README.md +63 -0
- package/build-multi-agent-system/SKILL.md +68 -0
- package/create-agent-launcher/SKILL.md +48 -0
- package/create-agent-launcher/examples/csharp-monorepo.md +123 -0
- package/create-agent-launcher/examples/typescript-cli.md +126 -0
- package/create-agent-launcher/guidelines/platform-claude.md +77 -0
- package/create-agent-launcher/guidelines/platform-codex.md +146 -0
- package/create-agent-launcher/guidelines/platform-copilot.md +74 -0
- package/create-agent-launcher/guidelines/platform-gemini.md +101 -0
- package/create-agent-launcher/guidelines/role-capabilities.md +108 -0
- package/create-agent-launcher/templates/README.md +17 -0
- package/create-agent-launcher/templates/agent-launcher.md +68 -0
- package/create-agent-launcher/templates/backend-developer-csharp.md +56 -0
- package/create-agent-launcher/templates/backend-developer-polyglot.md +62 -0
- package/create-agent-launcher/templates/backend-developer-ts.md +57 -0
- package/create-agent-launcher/templates/memory-keeper.md +37 -0
- package/create-agent-launcher/templates/requirement-analyzer.md +66 -0
- package/create-agent-launcher/templates/specialist-agent.md +34 -0
- package/create-agent-launcher/templates/task-verifier.md +56 -0
- package/create-agent-launcher/templates/tool-maker.md +33 -0
- package/create-agent-launcher/workflows/01-init.md +22 -0
- package/create-agent-launcher/workflows/02-analysis.md +26 -0
- package/create-agent-launcher/workflows/03-generation.md +29 -0
- package/create-agent-launcher/workflows/04-launcher.md +20 -0
- package/docker-verify.sh +266 -0
- package/docs/architecture.md +95 -0
- package/docs/philosophy.md +45 -0
- package/docs/reflection.md +34 -0
- package/docs/routing.md +45 -0
- package/docs/workflows/build-multi-agent-system.md +62 -0
- package/docs/workflows/create-agent-launcher.md +62 -0
- package/docs/workflows/environment-detection.md +63 -0
- package/docs/workflows/eval-harness.md +57 -0
- package/docs/workflows/fable-discipline.md +59 -0
- package/docs/workflows/fable-mode.md +32 -0
- package/docs/workflows/git-commit.md +62 -0
- package/docs/workflows/grill-me.md +62 -0
- package/docs/workflows/grill-with-docs.md +62 -0
- package/docs/workflows/harness-everything.md +92 -0
- package/docs/workflows/improve-codebase-architecture.md +60 -0
- package/docs/workflows/install-cognitive-os.md +60 -0
- package/docs/workflows/repo-docs.md +59 -0
- package/docs/workflows/rewrite-commits.md +60 -0
- package/docs/workflows/security-review.md +58 -0
- package/docs/workflows/self-evolve.md +59 -0
- package/docs/workflows/skill-creator.md +75 -0
- package/docs/workflows/skill-style.md +64 -0
- package/docs/workflows/tdd.md +39 -0
- package/docs/workflows/todo-driven-workflow.md +36 -0
- package/docs/workflows/using-git-worktrees.md +63 -0
- package/docs/workflows/verification-loop.md +69 -0
- package/docs/workflows/verify-before-claim.md +60 -0
- package/docs/workflows/zoom-out.md +62 -0
- package/environment-detection/SKILL.md +77 -0
- package/environment-detection/scripts/preflight.js +93 -0
- package/eval-framework/behavioral-test.js +105 -0
- package/eval-framework/cases/case1.json +5 -0
- package/eval-framework/cases/case2.json +5 -0
- package/eval-framework/cases/case3.json +5 -0
- package/eval-framework/cases/case4-zh-tier2.json +5 -0
- package/eval-framework/cases/case5-zh-tier3.json +5 -0
- package/eval-framework/mechanism-test.js +251 -0
- package/eval-framework/runner.js +56 -0
- package/eval-harness/SKILL.md +59 -0
- package/eval-harness/scripts/evaluate.js +61 -0
- package/fable-discipline/SKILL.md +38 -0
- package/fable-mode/CONTRACT-FORMAT.md +51 -0
- package/fable-mode/SKILL.md +51 -0
- package/fable-mode/agents/fable-orchestrator.md +92 -0
- package/fable-mode/agents/fable-verifier.md +32 -0
- package/fable-mode/agents/fable-worker-haiku.md +48 -0
- package/fable-mode/agents/fable-worker-sonnet.md +49 -0
- package/fable-mode/execution-guardrails/SKILL.md +87 -0
- package/fable-mode/fable-haiku/SKILL.md +48 -0
- package/git-commit/SKILL.md +55 -0
- package/git-commit/guides/ANGULAR_STYLE.md +61 -0
- package/git-commit/guides/COMMIT_GENERATION.md +24 -0
- package/git-commit/guides/LANGUAGE_DETECTION.md +16 -0
- package/git-commit/guides/MAIN_REPO.md +15 -0
- package/git-commit/guides/SUBMODULES.md +27 -0
- package/grill-me/SKILL.md +36 -0
- package/grill-with-docs/ADR-FORMAT.md +47 -0
- package/grill-with-docs/CONTEXT-FORMAT.md +63 -0
- package/grill-with-docs/SKILL.md +99 -0
- package/harness-everything/SKILL.md +141 -0
- package/harness-everything/examples/README.md +9 -0
- package/harness-everything/examples/todo-workflow.md +22 -0
- package/harness-everything/scripts/bootstrap.js +140 -0
- package/harness-everything/scripts/self-heal.js +189 -0
- package/harness-everything/scripts/tier-router.js +219 -0
- package/harness-everything/scripts/todo-cli.js +157 -0
- package/harness-everything/scripts/verify-gate.js +31 -0
- package/hooks/hooks.json +155 -0
- package/hooks/scripts/atomic-commit-check.js +68 -0
- package/hooks/scripts/boundary-guard.js +55 -0
- package/hooks/scripts/context-compact.js +114 -0
- package/hooks/scripts/contract-test.js +79 -0
- package/hooks/scripts/depth-guard.js +65 -0
- package/hooks/scripts/lib/harness-state.js +202 -0
- package/hooks/scripts/lib/platforms/claude.js +42 -0
- package/hooks/scripts/lib/platforms/codex.js +46 -0
- package/hooks/scripts/lib/platforms/continue.js +41 -0
- package/hooks/scripts/lib/platforms/copilot.js +41 -0
- package/hooks/scripts/lib/platforms/cursor.js +41 -0
- package/hooks/scripts/lib/platforms/hermes.js +32 -0
- package/hooks/scripts/lib/platforms/index.js +17 -0
- package/hooks/scripts/lib/platforms/worktrees.js +20 -0
- package/hooks/scripts/reset-circuit-breaker.js +30 -0
- package/hooks/scripts/rule-of-3-tracker.js +91 -0
- package/hooks/scripts/rule-of-3.js +176 -0
- package/hooks/scripts/state-persist.js +99 -0
- package/hooks/scripts/stop-gate.js +80 -0
- package/hooks/scripts/subagent-scope-guard.js +88 -0
- package/improve-codebase-architecture/SKILL.md +44 -0
- package/improve-codebase-architecture/guides/DEEPENING.md +37 -0
- package/improve-codebase-architecture/guides/HTML-REPORT.md +123 -0
- package/improve-codebase-architecture/guides/INTERFACE-DESIGN.md +44 -0
- package/improve-codebase-architecture/guides/LANGUAGE.md +53 -0
- package/install-cognitive-os/SKILL.md +95 -0
- package/package.json +22 -0
- package/repo-docs/SKILL.md +45 -0
- package/repo-docs/templates/agents-template.md +264 -0
- package/repo-docs/templates/knowledge-agents-template.md +50 -0
- package/repo-docs/templates/knowledge-readme-template.md +83 -0
- package/repo-docs/templates/manual-recon.md +117 -0
- package/repo-docs/templates/multi-skills-readme-template.md +72 -0
- package/repo-docs/templates/product-agents-template.md +78 -0
- package/repo-docs/templates/product-readme-template.md +95 -0
- package/repo-docs/templates/readme-template.md +136 -0
- package/repo-docs/templates/single-skill-readme-template.md +70 -0
- package/repo-docs/templates/skills-agents-template.md +60 -0
- package/rewrite-commits/SKILL.md +32 -0
- package/scripts/installer.js +649 -0
- package/scripts/lib/advisory-text.js +163 -0
- package/scripts/lib/claude-hooks.js +71 -0
- package/scripts/lib/gitignore.js +58 -0
- package/scripts/lib/manifest.js +82 -0
- package/scripts/lib/prompts.js +241 -0
- package/scripts/lib/skills.js +172 -0
- package/scripts/lib/workspace.js +28 -0
- package/security-review/SKILL.md +511 -0
- package/self-evolve/SKILL.md +54 -0
- package/self-evolve/scripts/persist-memory.js +40 -0
- package/self-evolve/scripts/self-regression.js +113 -0
- package/skill-creator/SKILL.md +91 -0
- package/skill-creator/references/quality-principles.md +81 -0
- package/skill-creator/references/testing-workflow.md +32 -0
- package/skill-style/SKILL.md +48 -0
- package/skill-style/STYLE.md +37 -0
- package/tdd/SKILL.md +53 -0
- package/tdd/guides/deep-modules.md +33 -0
- package/tdd/guides/interface-design.md +31 -0
- package/tdd/guides/mocking.md +59 -0
- package/tdd/guides/refactoring.md +10 -0
- package/tdd/guides/tests.md +61 -0
- package/todo-driven-workflow/SKILL.md +54 -0
- package/using-git-worktrees/SKILL.md +161 -0
- package/verification-loop/SKILL.md +138 -0
- package/verify-before-claim/SKILL.md +86 -0
- package/zoom-out/SKILL.md +90 -0
|
@@ -0,0 +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
|
+
};
|
|
@@ -0,0 +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 };
|
|
@@ -0,0 +1,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
|
+
if (addedPatterns.length > 0) {
|
|
29
|
+
const separator = content.length === 0 || content.endsWith('\n') ? '' : '\n';
|
|
30
|
+
const toAppend = addedPatterns.join('\n') + '\n';
|
|
31
|
+
fs.appendFileSync(gitignorePath, `${separator}${toAppend}`, 'utf8');
|
|
32
|
+
console.log(` ✅ Added auto-generated directories to .gitignore: ${addedPatterns.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.warn(` ⚠️ Failed to update .gitignore: ${err.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Only ever deletes a directory that is genuinely empty, and never recurses
|
|
40
|
+
// past one of `stopDirs` (workspace root / user home / filesystem root) - so
|
|
41
|
+
// this can only ever tidy up directories Harness itself just emptied out,
|
|
42
|
+
// never reach into a parent that still holds unrelated content.
|
|
43
|
+
function cleanEmptyDirs(dir, stopDirs) {
|
|
44
|
+
if (!fs.existsSync(dir)) return;
|
|
45
|
+
if ((stopDirs || []).includes(dir) || dir === path.parse(dir).root) return;
|
|
46
|
+
try {
|
|
47
|
+
const files = fs.readdirSync(dir);
|
|
48
|
+
if (files.length === 0) {
|
|
49
|
+
fs.rmdirSync(dir);
|
|
50
|
+
console.log(` ✅ Cleaned up empty directory: ${dir}`);
|
|
51
|
+
cleanEmptyDirs(path.dirname(dir), stopDirs);
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// ignore
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { ensureWorkspaceGitignorePatterns, cleanEmptyDirs };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Tracks exactly what this package installed, so uninstall can remove
|
|
2
|
+
// precisely those paths instead of sweeping shared directories that may also
|
|
3
|
+
// hold content from other tools, the user's own manual additions, or
|
|
4
|
+
// self-evolve's locally-generated skills.
|
|
5
|
+
//
|
|
6
|
+
// The manifest always lives at <platform-home>/harness-everything/manifest.json
|
|
7
|
+
// - e.g. .claude/harness-everything/manifest.json, .cursor/harness-everything/
|
|
8
|
+
// manifest.json, or (global scope) ~/.agents/harness-everything/manifest.json.
|
|
9
|
+
// harness-everything/ is a subfolder this package exclusively owns inside a
|
|
10
|
+
// directory that already belongs to that platform (or, for global scope, the
|
|
11
|
+
// shared ~/.agents convention) - nothing else creates a directory literally
|
|
12
|
+
// named "harness-everything" there, so nesting under it is what makes "the
|
|
13
|
+
// manifest can only describe our own installs" actually true.
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const PACKAGE_NAME = 'harness-everything';
|
|
18
|
+
const HARNESS_AUTHOR = 'Miya Daniel | Harness Core Team';
|
|
19
|
+
const HARNESS_DIR_NAME = 'harness-everything';
|
|
20
|
+
|
|
21
|
+
function getHarnessDir(homeDir) {
|
|
22
|
+
return path.join(homeDir, HARNESS_DIR_NAME);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getManifestPath(homeDir) {
|
|
26
|
+
return path.join(getHarnessDir(homeDir), 'manifest.json');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readManifest(manifestPath) {
|
|
30
|
+
if (!fs.existsSync(manifestPath)) {
|
|
31
|
+
return { package: PACKAGE_NAME, skills: [] };
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const data = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
35
|
+
if (!Array.isArray(data.skills)) data.skills = [];
|
|
36
|
+
return data;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return { package: PACKAGE_NAME, skills: [] };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeManifest(manifestPath, data) {
|
|
43
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
44
|
+
fs.writeFileSync(manifestPath, JSON.stringify(data, null, 2), 'utf8');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function recordSkillInstall(manifestPath, packageVersion, skillId, dirPath) {
|
|
48
|
+
const data = readManifest(manifestPath);
|
|
49
|
+
data.package = PACKAGE_NAME;
|
|
50
|
+
data.version = packageVersion;
|
|
51
|
+
data.updatedAt = new Date().toISOString();
|
|
52
|
+
const idx = data.skills.findIndex(s => s.dirPath === dirPath);
|
|
53
|
+
const entry = { id: skillId, dirPath, installedAt: new Date().toISOString() };
|
|
54
|
+
if (idx !== -1) data.skills[idx] = entry;
|
|
55
|
+
else data.skills.push(entry);
|
|
56
|
+
writeManifest(manifestPath, data);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Removes one skill entry, and - once no skills remain in it - deletes the
|
|
60
|
+
// manifest file itself rather than leaving an empty bookkeeping file behind.
|
|
61
|
+
function removeSkillFromManifest(manifestPath, dirPath) {
|
|
62
|
+
if (!fs.existsSync(manifestPath)) return;
|
|
63
|
+
const data = readManifest(manifestPath);
|
|
64
|
+
data.skills = data.skills.filter(s => s.dirPath !== dirPath);
|
|
65
|
+
if (data.skills.length === 0) {
|
|
66
|
+
fs.unlinkSync(manifestPath);
|
|
67
|
+
} else {
|
|
68
|
+
writeManifest(manifestPath, data);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
PACKAGE_NAME,
|
|
74
|
+
HARNESS_AUTHOR,
|
|
75
|
+
HARNESS_DIR_NAME,
|
|
76
|
+
getHarnessDir,
|
|
77
|
+
getManifestPath,
|
|
78
|
+
readManifest,
|
|
79
|
+
writeManifest,
|
|
80
|
+
recordSkillInstall,
|
|
81
|
+
removeSkillFromManifest,
|
|
82
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Terminal TUI primitives used by the interactive installer flow. No install
|
|
2
|
+
// or filesystem logic lives here - purely input/output.
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
|
|
5
|
+
function interactiveSelect(items, pageSize = items.length) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
let cursor = 0;
|
|
8
|
+
let scrollIndex = 0;
|
|
9
|
+
let visibleItems = [];
|
|
10
|
+
const isScrollable = items.length > pageSize;
|
|
11
|
+
|
|
12
|
+
function render() {
|
|
13
|
+
readline.cursorTo(process.stdout, 0);
|
|
14
|
+
|
|
15
|
+
if (isScrollable) {
|
|
16
|
+
if (cursor >= scrollIndex + pageSize) {
|
|
17
|
+
scrollIndex = cursor - pageSize + 1;
|
|
18
|
+
} else if (cursor < scrollIndex) {
|
|
19
|
+
scrollIndex = cursor;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
visibleItems = items.slice(scrollIndex, scrollIndex + pageSize);
|
|
24
|
+
const writeLines = [];
|
|
25
|
+
|
|
26
|
+
if (isScrollable) {
|
|
27
|
+
if (scrollIndex > 0) {
|
|
28
|
+
writeLines.push(` ▲ (more above)`);
|
|
29
|
+
} else {
|
|
30
|
+
writeLines.push(``); // Spacer
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
35
|
+
const itemIndex = scrollIndex + i;
|
|
36
|
+
const item = visibleItems[i];
|
|
37
|
+
const isSelected = item.checked ? '[x]' : '[ ]';
|
|
38
|
+
const isCurrent = itemIndex === cursor ? '> ' : ' ';
|
|
39
|
+
const desc = item.description ? ` - ${item.description}` : '';
|
|
40
|
+
const lineText = `${isCurrent}${isSelected} ${item.name || item.id}${desc}`;
|
|
41
|
+
const maxCols = process.stdout.columns || 80;
|
|
42
|
+
const truncated = lineText.length > maxCols - 5
|
|
43
|
+
? lineText.slice(0, maxCols - 8) + '...'
|
|
44
|
+
: lineText;
|
|
45
|
+
writeLines.push(truncated);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (isScrollable) {
|
|
49
|
+
if (scrollIndex + pageSize < items.length) {
|
|
50
|
+
writeLines.push(` ▼ (more below)`);
|
|
51
|
+
} else {
|
|
52
|
+
writeLines.push(``); // Spacer
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
writeLines.push(``);
|
|
57
|
+
writeLines.push(`(Use Up/Down Arrow keys to navigate, Space to toggle, Enter to confirm)`);
|
|
58
|
+
|
|
59
|
+
for (const line of writeLines) {
|
|
60
|
+
readline.clearLine(process.stdout, 0);
|
|
61
|
+
process.stdout.write(`${line}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
readline.emitKeypressEvents(process.stdin);
|
|
66
|
+
if (process.stdin.isTTY) {
|
|
67
|
+
process.stdin.setRawMode(true);
|
|
68
|
+
}
|
|
69
|
+
process.stdin.resume();
|
|
70
|
+
|
|
71
|
+
// Hide cursor
|
|
72
|
+
process.stdout.write('[?25l');
|
|
73
|
+
|
|
74
|
+
render();
|
|
75
|
+
|
|
76
|
+
function cleanup() {
|
|
77
|
+
// Show cursor again
|
|
78
|
+
process.stdout.write('[?25h');
|
|
79
|
+
process.stdin.removeListener('keypress', handleKeypress);
|
|
80
|
+
if (process.stdin.isTTY) {
|
|
81
|
+
process.stdin.setRawMode(false);
|
|
82
|
+
}
|
|
83
|
+
process.stdin.pause();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleKeypress(str, key) {
|
|
87
|
+
if (key) {
|
|
88
|
+
if (key.ctrl && key.name === 'c') {
|
|
89
|
+
cleanup();
|
|
90
|
+
process.exit(130);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const linesToMove = pageSize + (isScrollable ? 4 : 2);
|
|
94
|
+
|
|
95
|
+
if (key.name === 'up') {
|
|
96
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
97
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
98
|
+
render();
|
|
99
|
+
} else if (key.name === 'down') {
|
|
100
|
+
cursor = (cursor + 1) % items.length;
|
|
101
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
102
|
+
render();
|
|
103
|
+
} else if (key.name === 'space' || str === ' ') {
|
|
104
|
+
const itemIndex = cursor;
|
|
105
|
+
items[itemIndex].checked = !items[itemIndex].checked;
|
|
106
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
107
|
+
render();
|
|
108
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
109
|
+
cleanup();
|
|
110
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
111
|
+
for (let i = 0; i < linesToMove; i++) {
|
|
112
|
+
readline.clearLine(process.stdout, 0);
|
|
113
|
+
process.stdout.write('\n');
|
|
114
|
+
}
|
|
115
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
118
|
+
readline.clearLine(process.stdout, 0);
|
|
119
|
+
const item = visibleItems[i];
|
|
120
|
+
const isSelected = item.checked ? '✔' : ' ';
|
|
121
|
+
process.stdout.write(` ${isSelected} ${item.name || item.id}\n`);
|
|
122
|
+
}
|
|
123
|
+
if (isScrollable) {
|
|
124
|
+
readline.clearLine(process.stdout, 0);
|
|
125
|
+
process.stdout.write('\n');
|
|
126
|
+
}
|
|
127
|
+
readline.clearLine(process.stdout, 0);
|
|
128
|
+
process.stdout.write('\n');
|
|
129
|
+
resolve(items);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
process.stdin.on('keypress', handleKeypress);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function askQuestion(query) {
|
|
139
|
+
const rl = readline.createInterface({
|
|
140
|
+
input: process.stdin,
|
|
141
|
+
output: process.stdout,
|
|
142
|
+
});
|
|
143
|
+
return new Promise(resolve => {
|
|
144
|
+
rl.question(query, answer => {
|
|
145
|
+
rl.close();
|
|
146
|
+
resolve(answer.trim().toLowerCase());
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function interactiveSingleSelect(items) {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
let cursor = 0;
|
|
154
|
+
if (!items.some(i => i.checked)) {
|
|
155
|
+
items[0].checked = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function render() {
|
|
159
|
+
readline.cursorTo(process.stdout, 0);
|
|
160
|
+
for (let i = 0; i < items.length; i++) {
|
|
161
|
+
const item = items[i];
|
|
162
|
+
const isSelected = item.checked ? '(o)' : '( )';
|
|
163
|
+
const isCurrent = i === cursor ? '> ' : ' ';
|
|
164
|
+
readline.clearLine(process.stdout, 0);
|
|
165
|
+
process.stdout.write(`${isCurrent}${isSelected} ${item.name}\n`);
|
|
166
|
+
}
|
|
167
|
+
readline.clearLine(process.stdout, 0);
|
|
168
|
+
process.stdout.write(`\n`);
|
|
169
|
+
readline.clearLine(process.stdout, 0);
|
|
170
|
+
process.stdout.write(`(Use Up/Down Arrow keys to navigate, Space to select, Enter to confirm)\n`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
readline.emitKeypressEvents(process.stdin);
|
|
174
|
+
if (process.stdin.isTTY) {
|
|
175
|
+
process.stdin.setRawMode(true);
|
|
176
|
+
}
|
|
177
|
+
process.stdin.resume();
|
|
178
|
+
process.stdout.write('[?25l');
|
|
179
|
+
render();
|
|
180
|
+
|
|
181
|
+
function cleanup() {
|
|
182
|
+
process.stdout.write('[?25h');
|
|
183
|
+
process.stdin.removeListener('keypress', handleKeypress);
|
|
184
|
+
if (process.stdin.isTTY) {
|
|
185
|
+
process.stdin.setRawMode(false);
|
|
186
|
+
}
|
|
187
|
+
process.stdin.pause();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function handleKeypress(str, key) {
|
|
191
|
+
if (key) {
|
|
192
|
+
if (key.ctrl && key.name === 'c') {
|
|
193
|
+
cleanup();
|
|
194
|
+
process.exit(130);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const linesToMove = items.length + 2;
|
|
198
|
+
|
|
199
|
+
if (key.name === 'up') {
|
|
200
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
201
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
202
|
+
render();
|
|
203
|
+
} else if (key.name === 'down') {
|
|
204
|
+
cursor = (cursor + 1) % items.length;
|
|
205
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
206
|
+
render();
|
|
207
|
+
} else if (key.name === 'space' || str === ' ') {
|
|
208
|
+
items.forEach((item, idx) => {
|
|
209
|
+
item.checked = (idx === cursor);
|
|
210
|
+
});
|
|
211
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
212
|
+
render();
|
|
213
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
214
|
+
items.forEach((item, idx) => {
|
|
215
|
+
item.checked = (idx === cursor);
|
|
216
|
+
});
|
|
217
|
+
cleanup();
|
|
218
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
219
|
+
for (let i = 0; i < linesToMove; i++) {
|
|
220
|
+
readline.clearLine(process.stdout, 0);
|
|
221
|
+
process.stdout.write('\n');
|
|
222
|
+
}
|
|
223
|
+
readline.moveCursor(process.stdout, 0, -linesToMove);
|
|
224
|
+
|
|
225
|
+
for (let i = 0; i < items.length; i++) {
|
|
226
|
+
readline.clearLine(process.stdout, 0);
|
|
227
|
+
const item = items[i];
|
|
228
|
+
const isSelected = item.checked ? '✔' : ' ';
|
|
229
|
+
process.stdout.write(` ${isSelected} ${item.name}\n`);
|
|
230
|
+
}
|
|
231
|
+
readline.clearLine(process.stdout, 0);
|
|
232
|
+
process.stdout.write('\n');
|
|
233
|
+
resolve(items.find(item => item.checked).id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
process.stdin.on('keypress', handleKeypress);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = { interactiveSelect, interactiveSingleSelect, askQuestion };
|