heyspex 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/.claude-plugin/marketplace.json +14 -0
- package/bin/hs.mjs +181 -15
- package/dist/cli/hs-check.mjs +88 -46
- package/dist/cli/hs-claim.mjs +84 -39
- package/dist/cli/hs-connect.mjs +583 -129
- package/dist/cli/hs-defer.mjs +251 -30
- package/dist/cli/hs-dev.mjs +20 -8
- package/dist/cli/hs-env.mjs +62 -34
- package/dist/cli/hs-index-rebuild.mjs +288 -83
- package/dist/cli/hs-index-reconcile.mjs +397 -111
- package/dist/cli/hs-init.mjs +2143 -91
- package/dist/cli/hs-install.mjs +628 -106
- package/dist/cli/hs-join.mjs +686 -236
- package/dist/cli/hs-vault-pull.mjs +3189 -179
- package/dist/cli/hs-vault-push.mjs +6433 -0
- package/dist/cli/hs-vault.mjs +396 -178
- package/dist/cli/hs-workspace.mjs +289 -151
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -28
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -62
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +30 -28
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -29
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -70
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -35
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -27
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -23
- package/dist/cli-templates/harness-commands/claude/hs-view.md +28 -26
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -32
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -91
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +22 -22
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +56 -56
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +23 -23
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +64 -64
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +29 -29
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +21 -21
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +17 -17
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +24 -22
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +26 -26
- package/dist/cli-templates/harness-commands/cursor/prototype.md +86 -86
- package/docs/copilot-plugin-install.md +177 -0
- package/docs/driving-heyspex-from-copilot.md +165 -0
- package/package.json +21 -2
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -12
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -15
- package/plugins/heyspex/README.md +312 -0
- package/plugins/heyspex/bin/run-hook.mjs +20 -2
- package/plugins/heyspex/hooks/cursor-hooks.json +81 -0
- package/plugins/heyspex/hooks/hooks.json +94 -37
- package/scripts/claude-hooks/_intent-classifier-router.mjs +456 -410
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +5 -0
- package/scripts/claude-hooks/_run.mjs +316 -23
- package/scripts/claude-hooks/capture-execution-gate.mjs +51 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +2 -0
- package/scripts/claude-hooks/diagnose-capture-reminder.mjs +11 -0
- package/scripts/claude-hooks/directive-obligation-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +20 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +279 -247
- package/scripts/claude-hooks/session-end.mjs +2 -0
- package/scripts/claude-hooks/vault-mirror-catchup.mjs +11 -0
- package/scripts/claude-hooks/vault-write-tier-guard.mjs +11 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +5 -1
- package/scripts/cursor-hooks/manifest.mjs +46 -0
- package/scripts/heyspex-mode.mjs +20 -0
- package/scripts/hs-mode.mjs +1 -1
- package/scripts/hs-view.mjs +180 -17
- package/scripts/install-claude-code-hooks.mjs +3 -1
- package/scripts/install-copilot-hooks.mjs +288 -0
- package/scripts/lib/console-url-resolver.mjs +3 -2
- package/scripts/lib/customer-kit.mjs +327 -10
- package/scripts/lib/dev-mode.mjs +7 -6
- package/scripts/lib/directive-obligations.mjs +349 -0
- package/scripts/lib/hook-dispositions.mjs +210 -0
- package/scripts/lib/hook-vault-header.mjs +30 -0
- package/scripts/lib/hs-source-branch.mjs +196 -0
- package/scripts/lib/hs-source-log.mjs +2 -1
- package/scripts/lib/hs-source-preflight.mjs +18 -4
- package/scripts/lib/hs-source-push.mjs +386 -356
- package/scripts/lib/window-placement.mjs +109 -0
- package/spexharness/src/adapters/claude-code.mjs +11 -3
- package/spexharness/src/adapters/copilot.mjs +236 -0
- package/spexharness/src/adapters/detect-harness.mjs +34 -0
- package/spexharness/src/gates/_shell-write-targets.mjs +77 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +4 -3
- package/spexharness/src/gates/diagnose-capture-reminder.mjs +112 -0
- package/spexharness/src/gates/directive-obligation-gate.decide.mjs +132 -0
- package/spexharness/src/gates/directive-obligation-gate.mjs +183 -0
- package/spexharness/src/gates/vault-mirror-catchup.mjs +124 -0
- package/spexharness/src/gates/vault-write-tier-guard.decide.mjs +125 -0
- package/spexharness/src/gates/vault-write-tier-guard.mjs +42 -0
- package/spexharness/src/intent-classifier.mjs +4 -2
- package/spexharness/src/intent-directive.mjs +626 -541
- package/spexharness/src/rule-table.mjs +31 -1
- package/vendor/heyspex-mcp/account-switch.mjs +6 -5
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +31 -3
- package/vendor/heyspex-mcp/capture-console-client.mjs +37 -2
- package/vendor/heyspex-mcp/capture-focus.mjs +26 -2
- package/vendor/heyspex-mcp/capture-outbox.mjs +10 -4
- package/vendor/heyspex-mcp/capture-sources.mjs +195 -0
- package/vendor/heyspex-mcp/capture-tools.mjs +56 -11
- package/vendor/heyspex-mcp/claim-tools.mjs +25 -2
- package/vendor/heyspex-mcp/cli-verbs.mjs +19 -21
- package/vendor/heyspex-mcp/cli.mjs +7 -1
- package/vendor/heyspex-mcp/close-branch-report.mjs +102 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +197 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +192 -28
- package/vendor/heyspex-mcp/console-transport.mjs +7 -1
- package/vendor/heyspex-mcp/credentials.mjs +9 -8
- package/vendor/heyspex-mcp/dev-tools.mjs +7 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +35 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +2 -1
- package/vendor/heyspex-mcp/folder-trust-store.mjs +2 -1
- package/vendor/heyspex-mcp/http-transport.mjs +2 -0
- package/vendor/heyspex-mcp/list-tools.mjs +1 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +14 -1
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -523
- package/vendor/heyspex-mcp/obligation-backstop.mjs +103 -0
- package/vendor/heyspex-mcp/occupied-keys.mjs +1 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +19 -2
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -1708
- package/vendor/heyspex-mcp/plan-push.mjs +3 -2
- package/vendor/heyspex-mcp/preflight-gate.mjs +1 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +327 -325
- package/vendor/heyspex-mcp/procedure-tools.mjs +1181 -1159
- package/vendor/heyspex-mcp/promote-capture.mjs +2 -0
- package/vendor/heyspex-mcp/promote-research.mjs +6 -0
- package/vendor/heyspex-mcp/read-path.mjs +162 -6
- package/vendor/heyspex-mcp/request-context.mjs +25 -1
- package/vendor/heyspex-mcp/research-handlers.mjs +6 -4
- package/vendor/heyspex-mcp/roadmap-default.mjs +70 -0
- package/vendor/heyspex-mcp/session-owner.mjs +3 -2
- package/vendor/heyspex-mcp/session-tools.mjs +10 -0
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +8 -1
- package/vendor/heyspex-mcp/split-research.mjs +3 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +18 -1
- package/vendor/heyspex-mcp/summary-write-tools.mjs +5 -1
- package/vendor/heyspex-mcp/test-helpers.mjs +18 -3
- package/vendor/heyspex-mcp/tool-catalog.mjs +7 -1
- package/vendor/heyspex-mcp/tool-dispatch.mjs +18 -0
- package/vendor/heyspex-mcp/vault-io.mjs +720 -724
- package/vendor/heyspex-mcp/vault-resolve.mjs +197 -9
- package/vendor/heyspex-mcp/whoami.mjs +2 -1
- package/vendor/heyspex-mcp/write-outbox.mjs +512 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// `hs setup copilot` -- install HeySpex into GitHub Copilot CLI / cloud agent.
|
|
2
|
+
//
|
|
3
|
+
// Works from the published npm package only (node_modules/heyspex, or
|
|
4
|
+
// `npx heyspex setup copilot`) and from a platform checkout. The package root
|
|
5
|
+
// is resolved from THIS file's location, never from the cwd.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// node scripts/install-copilot-hooks.mjs # PLUGIN route (default)
|
|
9
|
+
// node scripts/install-copilot-hooks.mjs --hooks # HOOKS route: .github/hooks/heyspex.json
|
|
10
|
+
// node scripts/install-copilot-hooks.mjs --check # report active route; exit 1 if misconfigured
|
|
11
|
+
// node scripts/install-copilot-hooks.mjs --uninstall
|
|
12
|
+
// node scripts/install-copilot-hooks.mjs --print # agent-facing instructions to stdout
|
|
13
|
+
// --target <dir> repo to set up (default: cwd's repo root, walking up to .git / heyspex.yaml)
|
|
14
|
+
// --json (with --check) machine-readable report
|
|
15
|
+
//
|
|
16
|
+
// Single-source rule: the plugin OR the repo hooks file, never both (every hook would fire twice).
|
|
17
|
+
//
|
|
18
|
+
// Exit codes:
|
|
19
|
+
// 0 success / nothing to do / `copilot` missing (commands printed for the user to run)
|
|
20
|
+
// 1 failure: plugin route could not complete, --check found a problem, unexpected error
|
|
21
|
+
// 2 refused: --hooks while the heyspex plugin is installed for Copilot, or bad arguments
|
|
22
|
+
//
|
|
23
|
+
// Never prints a stack trace; every path ends with a next step.
|
|
24
|
+
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import { spawnSync } from 'node:child_process';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
|
|
31
|
+
import { detectDoubleFire, invokedDirectly, renderGithubHooksText } from './lib/customer-kit.mjs';
|
|
32
|
+
|
|
33
|
+
export const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
34
|
+
export const HOOKS_REL = ['.github', 'hooks', 'heyspex.json'];
|
|
35
|
+
const FALLBACK = { marketplace: 'heyspex-marketplace', plugin: 'heyspex' };
|
|
36
|
+
|
|
37
|
+
const fsDeps = {
|
|
38
|
+
readFile: (p) => {
|
|
39
|
+
try { return fs.readFileSync(p, 'utf8'); } catch { return null; }
|
|
40
|
+
},
|
|
41
|
+
listDir: (p) => {
|
|
42
|
+
try { return fs.readdirSync(p); } catch { return []; }
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Tiny .mjs twin of install.ts resolvePluginSpec: names come from the package's marketplace.json. */
|
|
47
|
+
export function resolvePluginSpec(platformRoot) {
|
|
48
|
+
const manifest = path.join(platformRoot, '.claude-plugin', 'marketplace.json');
|
|
49
|
+
const raw = fsDeps.readFile(manifest);
|
|
50
|
+
const out = { manifest, present: raw !== null, marketplace: FALLBACK.marketplace, plugin: FALLBACK.plugin };
|
|
51
|
+
if (raw !== null) {
|
|
52
|
+
try {
|
|
53
|
+
const j = JSON.parse(raw);
|
|
54
|
+
if (typeof j.name === 'string' && j.name.trim()) out.marketplace = j.name.trim();
|
|
55
|
+
const p = Array.isArray(j.plugins) ? j.plugins.find((x) => typeof x?.name === 'string' && x.name.trim()) : null;
|
|
56
|
+
if (p) out.plugin = p.name.trim();
|
|
57
|
+
} catch { /* keep fallback names */ }
|
|
58
|
+
}
|
|
59
|
+
out.spec = `${out.plugin}@${out.marketplace}`;
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Walk up from `start` to the nearest dir holding .git or heyspex.yaml; else `start`. */
|
|
64
|
+
export function findRepoRoot(start) {
|
|
65
|
+
let dir = path.resolve(start);
|
|
66
|
+
for (;;) {
|
|
67
|
+
if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'heyspex.yaml'))) return dir;
|
|
68
|
+
const up = path.dirname(dir);
|
|
69
|
+
if (up === dir) return path.resolve(start);
|
|
70
|
+
dir = up;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseArgs(argv) {
|
|
75
|
+
const a = { hooks: false, check: false, uninstall: false, print: false, json: false, target: null, bad: null };
|
|
76
|
+
for (let i = 0; i < argv.length; i++) {
|
|
77
|
+
const x = argv[i];
|
|
78
|
+
if (x === '--hooks') a.hooks = true;
|
|
79
|
+
else if (x === '--check') a.check = true;
|
|
80
|
+
else if (x === '--uninstall') a.uninstall = true;
|
|
81
|
+
else if (x === '--print') a.print = true;
|
|
82
|
+
else if (x === '--json') a.json = true;
|
|
83
|
+
else if (x === '--target') {
|
|
84
|
+
a.target = argv[++i] ?? null;
|
|
85
|
+
if (!a.target) a.bad = '--target needs a directory';
|
|
86
|
+
} else if (x.startsWith('--target=')) a.target = x.slice(9);
|
|
87
|
+
else a.bad = `unknown argument ${x}`;
|
|
88
|
+
}
|
|
89
|
+
return a;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function copilotRun(args) {
|
|
93
|
+
try {
|
|
94
|
+
// Windows needs a shell to resolve copilot.cmd; args are constants or the package path, so quote the latter.
|
|
95
|
+
const win = process.platform === 'win32';
|
|
96
|
+
const argv = win ? args.map((x) => (/[\s&()^]/.test(x) ? `"${x}"` : x)) : args;
|
|
97
|
+
const r = spawnSync('copilot', argv, { encoding: 'utf8', shell: win, timeout: 120000 });
|
|
98
|
+
return { status: r.error ? 127 : (r.status ?? 1), out: `${r.stdout ?? ''}${r.stderr ?? ''}`, missing: !!r.error };
|
|
99
|
+
} catch {
|
|
100
|
+
return { status: 127, out: '', missing: true };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function copilotOnPath() {
|
|
105
|
+
const r = copilotRun(['--version']);
|
|
106
|
+
return !r.missing && r.status === 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const q = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
110
|
+
const nextCommand = () => 'npx heyspex setup copilot';
|
|
111
|
+
|
|
112
|
+
/** Is the heyspex plugin installed for Copilot? Combines detectDoubleFire evidence with `copilot plugin list`. */
|
|
113
|
+
function pluginEvidence(targetRoot, copilotAvailable) {
|
|
114
|
+
const df = detectDoubleFire({ targetRoot, home: os.homedir(), deps: fsDeps });
|
|
115
|
+
// Only Copilot-side evidence counts: a Claude Code plugin install (~/.claude) is not a Copilot install.
|
|
116
|
+
const evidence = df.plugin === true ? df.evidence.filter((e) => /[/\\]\.copilot[/\\]/.test(e)) : [];
|
|
117
|
+
let plugin = evidence.length > 0;
|
|
118
|
+
if (copilotAvailable) {
|
|
119
|
+
const r = copilotRun(['plugin', 'list']);
|
|
120
|
+
if (r.status === 0 && /heyspex/i.test(r.out)) {
|
|
121
|
+
plugin = true;
|
|
122
|
+
evidence.push('`copilot plugin list` names heyspex');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { plugin, evidence };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Parse the hooks file; ours = every command row is a HeySpex row. */
|
|
129
|
+
function hooksFileState(target) {
|
|
130
|
+
const file = path.join(target, ...HOOKS_REL);
|
|
131
|
+
const raw = fsDeps.readFile(file);
|
|
132
|
+
if (raw === null) return { file, exists: false, ours: false, current: false };
|
|
133
|
+
let ours = false;
|
|
134
|
+
try {
|
|
135
|
+
const j = JSON.parse(raw);
|
|
136
|
+
const cmds = Object.values(j?.hooks ?? {}).flat().map((r) => r?.bash ?? r?.command).filter((c) => typeof c === 'string');
|
|
137
|
+
ours = cmds.length > 0 && cmds.every((c) => /claude-hooks\/_run\.mjs/.test(c));
|
|
138
|
+
} catch { /* foreign / unparseable */ }
|
|
139
|
+
return { file, exists: true, ours, current: raw.replace(/\r\n/g, '\n') === renderGithubHooksText() };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function manualCommands(spec) {
|
|
143
|
+
return [
|
|
144
|
+
`copilot plugin marketplace add ${q(PACKAGE_ROOT)}`,
|
|
145
|
+
`copilot plugin install ${spec}`,
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function doPrint(out) {
|
|
150
|
+
out(`Next: run \`${nextCommand()}\` (plugin) or \`${nextCommand()} --hooks\` (cloud agent) in this repo, then /restart.`);
|
|
151
|
+
const doc = fsDeps.readFile(path.join(PACKAGE_ROOT, 'docs', 'copilot-plugin-install.md'));
|
|
152
|
+
if (doc !== null) { out(''); out(doc.replace(/\s+$/, '')); return 0; }
|
|
153
|
+
const spec = resolvePluginSpec(PACKAGE_ROOT);
|
|
154
|
+
out('');
|
|
155
|
+
out('# Installing HeySpex in GitHub Copilot CLI (built-in fallback; docs/copilot-plugin-install.md not found)');
|
|
156
|
+
out('Run these, in order, and stop at the first failure:');
|
|
157
|
+
for (const c of manualCommands(spec.spec)) out(` ${c}`);
|
|
158
|
+
out(' npx hs doctor');
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function doCheck(a, target, out) {
|
|
163
|
+
const spec = resolvePluginSpec(PACKAGE_ROOT);
|
|
164
|
+
const hasCopilot = copilotOnPath();
|
|
165
|
+
const ev = pluginEvidence(target, hasCopilot);
|
|
166
|
+
const hooks = hooksFileState(target);
|
|
167
|
+
const route = ev.plugin && hooks.exists ? 'both' : ev.plugin ? 'plugin' : hooks.exists ? 'hooks' : 'none';
|
|
168
|
+
const problems = [];
|
|
169
|
+
if (route === 'both') problems.push('double-fire: the plugin AND .github/hooks/heyspex.json are active, so every hook runs twice. Run `npx heyspex setup copilot --uninstall` (hooks file) or `copilot plugin uninstall heyspex`.');
|
|
170
|
+
if (!spec.present) problems.push(`marketplace manifest missing: ${spec.manifest}`);
|
|
171
|
+
if (route === 'hooks' && !hooks.ours) problems.push('.github/hooks/heyspex.json is not a HeySpex file');
|
|
172
|
+
if (route === 'hooks' && hooks.ours && !hooks.current) problems.push('.github/hooks/heyspex.json is stale; re-run `npx heyspex setup copilot --hooks`');
|
|
173
|
+
if (route === 'none') problems.push(`no route active; run \`${nextCommand()}\` (plugin) or \`${nextCommand()} --hooks\``);
|
|
174
|
+
const report = { route, plugin: ev.plugin, pluginEvidence: ev.evidence, hooksFile: hooks.exists, hooksFilePath: hooks.file, hooksCurrent: hooks.current, marketplaceManifest: spec.present, marketplacePath: spec.manifest, copilotOnPath: hasCopilot, problems };
|
|
175
|
+
if (a.json) out(JSON.stringify(report, null, 2));
|
|
176
|
+
else {
|
|
177
|
+
out(`hs setup copilot --check (target ${target})`);
|
|
178
|
+
out(` route: ${route}${route === 'hooks' ? ' (hooks route: .github/hooks/heyspex.json)' : route === 'plugin' ? ' (plugin route)' : ''}`);
|
|
179
|
+
out(` plugin installed: ${ev.plugin ? 'yes' : 'no'}${ev.evidence.length ? ` (${ev.evidence.join('; ')})` : ''}`);
|
|
180
|
+
out(` hooks file: ${hooks.exists ? `present${hooks.ours ? '' : ' (not HeySpex)'}${hooks.current ? ', current' : ', stale'}` : 'absent'}`);
|
|
181
|
+
out(` marketplace manifest in package: ${spec.present ? 'present' : 'MISSING'}`);
|
|
182
|
+
out(` copilot CLI on PATH: ${hasCopilot ? 'yes' : 'no'}`);
|
|
183
|
+
for (const p of problems) out(` PROBLEM: ${p}`);
|
|
184
|
+
if (!problems.length) out(' ok');
|
|
185
|
+
}
|
|
186
|
+
return problems.length ? 1 : 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function doHooks(target, out, err) {
|
|
190
|
+
const ev = pluginEvidence(target, copilotOnPath());
|
|
191
|
+
if (ev.plugin) {
|
|
192
|
+
err(`hs setup copilot --hooks refused: the heyspex plugin is already installed for Copilot (${ev.evidence.join('; ')}).`);
|
|
193
|
+
err('Plugin OR hooks, never both (every hook would fire twice). Remove the plugin first: `copilot plugin uninstall heyspex`.');
|
|
194
|
+
return 2;
|
|
195
|
+
}
|
|
196
|
+
const hooks = hooksFileState(target);
|
|
197
|
+
if (hooks.exists && !hooks.ours) {
|
|
198
|
+
err(`hs setup copilot --hooks refused: ${hooks.file} exists and is not a HeySpex file; not overwriting it.`);
|
|
199
|
+
return 2;
|
|
200
|
+
}
|
|
201
|
+
if (hooks.exists && hooks.current) {
|
|
202
|
+
out(`already current: ${hooks.file}`);
|
|
203
|
+
} else {
|
|
204
|
+
fs.mkdirSync(path.dirname(hooks.file), { recursive: true });
|
|
205
|
+
fs.writeFileSync(hooks.file, renderGithubHooksText(), 'utf8');
|
|
206
|
+
out(`${hooks.exists ? 'updated' : 'wrote'} ${hooks.file}`);
|
|
207
|
+
}
|
|
208
|
+
out('Commit .github/hooks/heyspex.json so the Copilot cloud agent and teammates get it. Commands resolve node_modules/heyspex first and exit 0 when it is absent.');
|
|
209
|
+
out('Next: `npx heyspex doctor`, then /restart in your Copilot session.');
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function doPlugin(target, out, err) {
|
|
214
|
+
const spec = resolvePluginSpec(PACKAGE_ROOT);
|
|
215
|
+
if (!spec.present) {
|
|
216
|
+
err(`hs setup copilot: marketplace manifest not found at ${spec.manifest}`);
|
|
217
|
+
err('The heyspex package looks incomplete. Reinstall it: `npm install heyspex@latest`, then re-run.');
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
const hooks = hooksFileState(target);
|
|
221
|
+
if (hooks.exists) {
|
|
222
|
+
out(`WARNING: ${hooks.file} already exists. Plugin OR hooks, never both (every hook would fire twice). Remove it with \`npx heyspex setup copilot --uninstall\` after the plugin is installed.`);
|
|
223
|
+
}
|
|
224
|
+
const cmds = manualCommands(spec.spec);
|
|
225
|
+
if (!copilotOnPath()) {
|
|
226
|
+
out('`copilot` is not on PATH. Install GitHub Copilot CLI, then run these two commands:');
|
|
227
|
+
for (const c of cmds) out(` ${c}`);
|
|
228
|
+
out('Then /restart in Copilot and verify with `copilot plugin list` and `npx heyspex doctor`.');
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
const add = copilotRun(['plugin', 'marketplace', 'add', PACKAGE_ROOT]);
|
|
232
|
+
if (add.status !== 0 && !/already/i.test(add.out)) {
|
|
233
|
+
err(`copilot plugin marketplace add failed:\n${add.out.trim().slice(-600)}`);
|
|
234
|
+
err('Run these by hand and report the output:');
|
|
235
|
+
for (const c of cmds) err(` ${c}`);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
const inst = copilotRun(['plugin', 'install', spec.spec]);
|
|
239
|
+
if (inst.status !== 0 && !/already/i.test(inst.out)) {
|
|
240
|
+
err(`copilot plugin install failed:\n${inst.out.trim().slice(-600)}`);
|
|
241
|
+
err('Run this by hand and report the output:');
|
|
242
|
+
err(` ${cmds[1]}`);
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
out(`installed ${spec.spec} (marketplace from ${PACKAGE_ROOT})`);
|
|
246
|
+
out('Next: /restart in Copilot, then `copilot plugin list` and `npx heyspex doctor`.');
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function doUninstall(target, out) {
|
|
251
|
+
const hooks = hooksFileState(target);
|
|
252
|
+
if (!hooks.exists) out(`no hooks file at ${hooks.file}`);
|
|
253
|
+
else if (!hooks.ours) out(`left ${hooks.file} alone: it is not a HeySpex-only file`);
|
|
254
|
+
else { fs.rmSync(hooks.file, { force: true }); out(`removed ${hooks.file}`); }
|
|
255
|
+
const cmd = 'copilot plugin uninstall heyspex';
|
|
256
|
+
if (copilotOnPath()) {
|
|
257
|
+
const r = copilotRun(['plugin', 'uninstall', 'heyspex']);
|
|
258
|
+
out(r.status === 0 ? `ran \`${cmd}\`` : `\`${cmd}\` did not complete (it may not be installed): ${r.out.trim().slice(-200)}`);
|
|
259
|
+
} else out(`To remove the plugin, run: ${cmd}`);
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function main(argv = process.argv.slice(2), io = {}) {
|
|
264
|
+
const out = io.out ?? ((s) => process.stdout.write(`${s}\n`));
|
|
265
|
+
const err = io.err ?? ((s) => process.stderr.write(`${s}\n`));
|
|
266
|
+
try {
|
|
267
|
+
const a = parseArgs(argv);
|
|
268
|
+
if (a.bad) {
|
|
269
|
+
err(`hs setup copilot: ${a.bad}`);
|
|
270
|
+
err('usage: hs setup copilot [--hooks] [--check [--json]] [--uninstall] [--print] [--target <dir>]');
|
|
271
|
+
return 2;
|
|
272
|
+
}
|
|
273
|
+
if (a.print) return doPrint(out);
|
|
274
|
+
const target = a.target ? path.resolve(a.target) : findRepoRoot(process.env.HS_SETUP_TARGET?.trim() || process.cwd());
|
|
275
|
+
if (a.check) return doCheck(a, target, out);
|
|
276
|
+
if (a.uninstall) return doUninstall(target, out);
|
|
277
|
+
if (a.hooks) return doHooks(target, out, err);
|
|
278
|
+
return doPlugin(target, out, err);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
err(`hs setup copilot: unexpected failure: ${e?.message ?? e}`);
|
|
281
|
+
err('Manual fallback: copilot plugin marketplace add <node_modules/heyspex>; copilot plugin install heyspex@heyspex-marketplace');
|
|
282
|
+
return 1;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (invokedDirectly(import.meta.url, 'install-copilot-hooks.mjs')) {
|
|
287
|
+
process.exit(main());
|
|
288
|
+
}
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
* isn't installed in their execution context. Stays SYNCHRONOUS and does zero
|
|
55
55
|
* network I/O: it runs on every console request.
|
|
56
56
|
*/
|
|
57
|
+
import { heyspexHome } from './heyspex-home.mjs';
|
|
57
58
|
import { readFileSync } from 'node:fs';
|
|
58
59
|
import { homedir } from 'node:os';
|
|
59
60
|
import { join } from 'node:path';
|
|
@@ -120,7 +121,7 @@ function warnStaleLoopbackCredentialsOnce(credentialsUrl, derivedUrl, { log }) {
|
|
|
120
121
|
* consumers (some of which run standalone, outside the npm workspace's
|
|
121
122
|
* hoisted `node_modules`) can call it with zero new dependencies.
|
|
122
123
|
*/
|
|
123
|
-
export function readCredentialsConsoleUrl({ homeDir =
|
|
124
|
+
export function readCredentialsConsoleUrl({ homeDir = heyspexHome(), readFile = defaultReadFile } = {}) {
|
|
124
125
|
try {
|
|
125
126
|
const raw = readFile(join(homeDir, '.heyspex', 'credentials.yaml'));
|
|
126
127
|
const match = String(raw ?? '').match(/^console_url:\s*["']?([^"'\r\n#]+)/m);
|
|
@@ -241,7 +242,7 @@ function resolveModeEnvelope({ env, homeDir }) {
|
|
|
241
242
|
export function resolveConsoleUrl({
|
|
242
243
|
repoRoot,
|
|
243
244
|
env = process.env,
|
|
244
|
-
homeDir =
|
|
245
|
+
homeDir = heyspexHome(),
|
|
245
246
|
strict = false,
|
|
246
247
|
devMode,
|
|
247
248
|
log = (line) => process.stderr.write(line),
|