@alecsibilia/luca 13.0.0-alpha.1 → 13.0.0-alpha.10
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/dist/chunks/branch.mjs +6 -6
- package/dist/chunks/checks.mjs +6 -6
- package/dist/chunks/claim-verify.mjs +1 -1
- package/dist/chunks/classify.mjs +1 -1
- package/dist/chunks/confidence.mjs +78 -8
- package/dist/chunks/config-version-skew.mjs +97 -0
- package/dist/chunks/doctor.mjs +17 -1
- package/dist/chunks/hook.mjs +140 -12
- package/dist/chunks/init.mjs +96 -52
- package/dist/chunks/legacy-claude-artifacts.mjs +105 -0
- package/dist/chunks/legacy-package.mjs +47 -0
- package/dist/chunks/luca-gitignore.mjs +93 -0
- package/dist/chunks/muninn-mcp.mjs +78 -0
- package/dist/chunks/muninndb-health.mjs +1 -1
- package/dist/chunks/phase.mjs +28 -8
- package/dist/chunks/pr-review.mjs +6 -6
- package/dist/chunks/preferences.mjs +6 -6
- package/dist/chunks/repair.mjs +1 -1
- package/dist/chunks/repo.mjs +6 -6
- package/dist/chunks/retro.mjs +5 -5
- package/dist/chunks/roadmap.mjs +6 -6
- package/dist/chunks/rules.mjs +5 -5
- package/dist/chunks/shared-tmp-payloads.mjs +72 -0
- package/dist/chunks/stale-global-symlinks.mjs +78 -0
- package/dist/chunks/state.mjs +47 -7
- package/dist/chunks/stray-local-install.mjs +15 -7
- package/dist/chunks/telemetry.mjs +1 -1
- package/dist/chunks/todo.mjs +6 -6
- package/dist/chunks/vault-config-location.mjs +96 -0
- package/dist/chunks/vault-init.mjs +42 -29
- package/dist/chunks/verification.mjs +4 -4
- package/dist/chunks/workflow.mjs +6 -6
- package/dist/claude/.claude/agents/architect.md +44 -1
- package/dist/claude/.claude/agents/debater.md +89 -0
- package/dist/claude/.claude/agents/discussion.md +14 -26
- package/dist/claude/.claude/agents/execute.md +15 -3
- package/dist/claude/.claude/agents/executor.md +12 -23
- package/dist/claude/.claude/agents/finalize.md +33 -34
- package/dist/claude/.claude/agents/learner.md +32 -71
- package/dist/claude/.claude/agents/plan-reviewer.md +6 -9
- package/dist/claude/.claude/agents/research.md +2 -2
- package/dist/claude/.claude/agents/researcher.md +6 -9
- package/dist/claude/.claude/agents/review.md +27 -17
- package/dist/claude/.claude/agents/reviewer.md +19 -15
- package/dist/claude/.claude/agents/shadow-scanner.md +25 -27
- package/dist/claude/.claude/agents/test-writer.md +90 -0
- package/dist/claude/.claude/agents/triage.md +6 -7
- package/dist/claude/.claude/agents/verifier.md +6 -5
- package/dist/claude/.claude/commands/gh-pr-address.md +10 -10
- package/dist/claude/.claude/commands/lu-review.md +1 -1
- package/dist/claude/.claude/commands/lu.md +38 -15
- package/dist/claude/.claude/commands/milestone-new.md +4 -4
- package/dist/claude/.claude/commands/phase-execute.md +1 -1
- package/dist/claude/.claude/commands/phase-plan.md +1 -1
- package/dist/claude/.claude/commands/repo-cleanup.md +10 -7
- package/dist/claude/.claude/hooks/context-refresher.ts +14046 -252
- package/dist/claude/.claude/hooks/continuation-messages.ts +14023 -190
- package/dist/claude/.claude/hooks/pipeline-guard.ts +14015 -162
- package/dist/claude/.claude/luca-statusline.ts +146 -0
- package/dist/claude/skills/caveman/SKILL.md +58 -0
- package/dist/claude/skills/gh-issue-triage/SKILL.md +2 -2
- package/dist/claude/skills/gh-pr-address/SKILL.md +10 -10
- package/dist/claude/skills/gh-prepare/SKILL.md +1 -1
- package/dist/claude/skills/lu/SKILL.md +70 -19
- package/dist/claude/skills/lu-review/SKILL.md +1 -1
- package/dist/claude/skills/luca-init/SKILL.md +2 -2
- package/dist/claude/skills/luca-write-surface/SKILL.md +19 -3
- package/dist/claude/skills/milestone-audit/SKILL.md +41 -61
- package/dist/claude/skills/milestone-complete/SKILL.md +10 -3
- package/dist/claude/skills/phase-discuss/SKILL.md +16 -6
- package/dist/claude/skills/phase-execute/SKILL.md +92 -85
- package/dist/claude/skills/phase-plan/SKILL.md +27 -17
- package/dist/claude/skills/phase-research/SKILL.md +9 -1
- package/dist/claude/skills/post-init-tour/SKILL.md +1 -1
- package/dist/claude/skills/project-new/SKILL.md +21 -79
- package/dist/claude/skills/quick/SKILL.md +9 -14
- package/dist/claude/skills/repo-audit/SKILL.md +5 -5
- package/dist/claude/skills/repo-cleanup/SKILL.md +10 -7
- package/dist/claude/skills/session-pause/SKILL.md +1 -1
- package/dist/claude/skills/session-plan/SKILL.md +9 -9
- package/dist/claude/skills/workflow-save/SKILL.md +4 -4
- package/dist/index.mjs +1 -1
- package/dist/shared/{luca.B3saVjJm.mjs → luca.BHov6l1O.mjs} +25 -5
- package/dist/shared/{luca.BYdjkfnz.mjs → luca.BfjhRHhj.mjs} +1 -1
- package/dist/shared/{luca.DjDTeDCi.mjs → luca.BtwZ3m3l.mjs} +253 -77
- package/dist/shared/{luca.CRmaAfXR.mjs → luca.CI5IvdwC.mjs} +92 -27
- package/dist/shared/luca.ChU85KSu.mjs +32 -0
- package/dist/shared/{luca.BmhNkYe2.mjs → luca.DGgeB5U5.mjs} +2 -2
- package/dist/shared/{luca.TSMg1t7I.mjs → luca.DbrDoFFH.mjs} +1 -1
- package/dist/shared/{luca.CrXzXueR.mjs → luca.GSXJf0Tv.mjs} +1 -1
- package/dist/shared/{luca.HZxBTBgD.mjs → luca.LySXBNNL.mjs} +2 -2
- package/dist/shared/{luca.C4gMUoBd.mjs → luca.Xq97ytct.mjs} +3 -3
- package/dist/shared/luca.cizL1tYz.mjs +102 -0
- package/dist/shared/luca.ig-qt2eC.mjs +184 -0
- package/package.json +4 -4
- package/dist/claude/skills/autopilot/SKILL.md +0 -1299
- package/dist/shared/luca.B3Mimc0P.mjs +0 -52
package/dist/chunks/branch.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import 'zod';
|
|
3
|
-
import '../shared/luca.
|
|
3
|
+
import '../shared/luca.CI5IvdwC.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:fs/promises';
|
|
6
6
|
import 'node:path';
|
|
@@ -8,13 +8,13 @@ import 'node:crypto';
|
|
|
8
8
|
import 'node:module';
|
|
9
9
|
import 'node:url';
|
|
10
10
|
import 'node:child_process';
|
|
11
|
-
import { r as runWriteHandler,
|
|
11
|
+
import { r as runWriteHandler, u as lucaBranchGuardTool } from '../shared/luca.BtwZ3m3l.mjs';
|
|
12
|
+
import '../shared/luca.ig-qt2eC.mjs';
|
|
12
13
|
import 'node:os';
|
|
13
|
-
import '../shared/luca.
|
|
14
|
-
import '../shared/luca.
|
|
15
|
-
import '../shared/luca.
|
|
14
|
+
import '../shared/luca.GSXJf0Tv.mjs';
|
|
15
|
+
import '../shared/luca.LySXBNNL.mjs';
|
|
16
|
+
import '../shared/luca.DbrDoFFH.mjs';
|
|
16
17
|
import '../shared/luca.CQ3g1xrD.mjs';
|
|
17
|
-
import '../shared/luca.B3Mimc0P.mjs';
|
|
18
18
|
|
|
19
19
|
const guardCommand = defineCommand({
|
|
20
20
|
meta: {
|
package/dist/chunks/checks.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import 'zod';
|
|
3
|
-
import '../shared/luca.
|
|
3
|
+
import '../shared/luca.CI5IvdwC.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:fs/promises';
|
|
6
6
|
import 'node:path';
|
|
@@ -8,13 +8,13 @@ import 'node:crypto';
|
|
|
8
8
|
import 'node:module';
|
|
9
9
|
import 'node:url';
|
|
10
10
|
import 'node:child_process';
|
|
11
|
-
import {
|
|
11
|
+
import { f as readJsonPayload, r as runWriteHandler, t as lucaChecksRunTool } from '../shared/luca.BtwZ3m3l.mjs';
|
|
12
|
+
import '../shared/luca.ig-qt2eC.mjs';
|
|
12
13
|
import 'node:os';
|
|
13
|
-
import '../shared/luca.
|
|
14
|
-
import '../shared/luca.
|
|
15
|
-
import '../shared/luca.
|
|
14
|
+
import '../shared/luca.GSXJf0Tv.mjs';
|
|
15
|
+
import '../shared/luca.LySXBNNL.mjs';
|
|
16
|
+
import '../shared/luca.DbrDoFFH.mjs';
|
|
16
17
|
import '../shared/luca.CQ3g1xrD.mjs';
|
|
17
|
-
import '../shared/luca.B3Mimc0P.mjs';
|
|
18
18
|
|
|
19
19
|
const runCommand = defineCommand({
|
|
20
20
|
meta: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { resolve } from 'pathe';
|
|
3
|
-
import '../shared/luca.
|
|
3
|
+
import '../shared/luca.CI5IvdwC.mjs';
|
|
4
4
|
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
5
5
|
import 'node:fs/promises';
|
|
6
6
|
import { join } from 'node:path';
|
package/dist/chunks/classify.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import { C as ClassifyComplexityInputSchema } from '../shared/luca.
|
|
2
|
+
import { C as ClassifyComplexityInputSchema } from '../shared/luca.CI5IvdwC.mjs';
|
|
3
3
|
import 'node:fs';
|
|
4
4
|
import 'node:fs/promises';
|
|
5
5
|
import 'node:path';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import '../shared/luca.
|
|
3
|
-
import { l as loadCurrentState, r as resolveActiveSlug } from '../shared/luca.
|
|
2
|
+
import '../shared/luca.CI5IvdwC.mjs';
|
|
3
|
+
import { l as loadCurrentState, r as resolveActiveSlug } from '../shared/luca.GSXJf0Tv.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:fs/promises';
|
|
6
6
|
import 'node:path';
|
|
@@ -8,15 +8,46 @@ import 'node:crypto';
|
|
|
8
8
|
import 'node:module';
|
|
9
9
|
import 'node:url';
|
|
10
10
|
import 'node:child_process';
|
|
11
|
-
import {
|
|
11
|
+
import { r as readConfidenceJournal, b as renderConfidenceJournalMarkdown, g as getConfidenceSummary } from '../shared/luca.LySXBNNL.mjs';
|
|
12
12
|
import { l as logger } from '../shared/luca.dM-MKlNE.mjs';
|
|
13
13
|
import 'zod';
|
|
14
|
-
import {
|
|
14
|
+
import { f as readJsonPayload, r as runWriteHandler, w as lucaConfidenceLogTool } from '../shared/luca.BtwZ3m3l.mjs';
|
|
15
|
+
import '../shared/luca.ig-qt2eC.mjs';
|
|
15
16
|
import 'node:os';
|
|
16
|
-
import '../shared/luca.
|
|
17
|
+
import '../shared/luca.DbrDoFFH.mjs';
|
|
17
18
|
import 'consola';
|
|
18
19
|
import '../shared/luca.CQ3g1xrD.mjs';
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
function selectConfidenceGateActions(entries) {
|
|
22
|
+
const auto = [];
|
|
23
|
+
const research = [];
|
|
24
|
+
const ask = [];
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (entry.resolution) {
|
|
27
|
+
if (entry.resolution === "auto") auto.push(entry);
|
|
28
|
+
else if (entry.resolution === "research") research.push(entry);
|
|
29
|
+
else if (entry.resolution === "ask") ask.push(entry);
|
|
30
|
+
else {
|
|
31
|
+
entry.resolution;
|
|
32
|
+
ask.push(entry);
|
|
33
|
+
}
|
|
34
|
+
} else if (entry.confidence === "high") {
|
|
35
|
+
auto.push(entry);
|
|
36
|
+
} else if (entry.confidence === "medium") {
|
|
37
|
+
auto.push(entry);
|
|
38
|
+
} else if (entry.confidence === "low" && entry.researchable === true) {
|
|
39
|
+
research.push(entry);
|
|
40
|
+
} else {
|
|
41
|
+
ask.push(entry);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
auto,
|
|
46
|
+
research,
|
|
47
|
+
ask,
|
|
48
|
+
counts: { auto: auto.length, research: research.length, ask: ask.length }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
20
51
|
|
|
21
52
|
async function resolveSlug(opts) {
|
|
22
53
|
if (opts.explicit) return opts.explicit;
|
|
@@ -87,6 +118,14 @@ const logCommand = defineCommand({
|
|
|
87
118
|
"review-hint": {
|
|
88
119
|
type: "string",
|
|
89
120
|
description: "Optional. Suggested focus area for a human reviewer."
|
|
121
|
+
},
|
|
122
|
+
researchable: {
|
|
123
|
+
type: "boolean",
|
|
124
|
+
description: "Optional. Set true when the ambiguity is factual and resolvable by automated research; absent/false means human judgment is required (gate routes to ask)."
|
|
125
|
+
},
|
|
126
|
+
resolution: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description: "Optional. Explicit gate routing override: auto | research | ask. Overrides confidence-derived bucketing when set."
|
|
90
129
|
}
|
|
91
130
|
},
|
|
92
131
|
async run({ args }) {
|
|
@@ -101,6 +140,13 @@ const logCommand = defineCommand({
|
|
|
101
140
|
}
|
|
102
141
|
payload = fromFile;
|
|
103
142
|
} else {
|
|
143
|
+
const resolution = args.resolution;
|
|
144
|
+
if (resolution !== void 0 && resolution !== "auto" && resolution !== "research" && resolution !== "ask") {
|
|
145
|
+
logger.error(
|
|
146
|
+
`luca confidence log: --resolution must be one of: auto, research, ask (got "${resolution}"). Run \`luca confidence log --help\` for usage.`
|
|
147
|
+
);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
104
150
|
payload = {
|
|
105
151
|
phase: args.phase,
|
|
106
152
|
wave: args.wave !== void 0 ? Number(args.wave) : void 0,
|
|
@@ -112,7 +158,9 @@ const logCommand = defineCommand({
|
|
|
112
158
|
reasoning: args.reasoning,
|
|
113
159
|
risk: args.risk,
|
|
114
160
|
files: splitCsv(args.files),
|
|
115
|
-
...args["review-hint"] !== void 0 ? { reviewHint: args["review-hint"] } : {}
|
|
161
|
+
...args["review-hint"] !== void 0 ? { reviewHint: args["review-hint"] } : {},
|
|
162
|
+
...args.researchable !== void 0 ? { researchable: args.researchable } : {},
|
|
163
|
+
...resolution !== void 0 ? { resolution } : {}
|
|
116
164
|
};
|
|
117
165
|
}
|
|
118
166
|
await runWriteHandler(
|
|
@@ -183,6 +231,27 @@ const renderCommand = defineCommand({
|
|
|
183
231
|
`);
|
|
184
232
|
}
|
|
185
233
|
});
|
|
234
|
+
const gateCommand = defineCommand({
|
|
235
|
+
meta: {
|
|
236
|
+
name: "gate",
|
|
237
|
+
description: "Bucket a phase's confidence entries into gate actions and emit a JSON object shaped { auto: [...], research: [...], ask: [...], counts: { auto, research, ask } } to stdout. Each entry lands in exactly one bucket: explicit resolution overrides first, then high/medium \u2192 auto, low+researchable \u2192 research, otherwise \u2192 ask (fail-toward-human)."
|
|
238
|
+
},
|
|
239
|
+
args: {
|
|
240
|
+
slug: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Phase slug (defaults to active phase)."
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
async run({ args }) {
|
|
246
|
+
const cwd = process.cwd();
|
|
247
|
+
const slug = await resolveSlug({ explicit: args.slug, cwd });
|
|
248
|
+
const actions = selectConfidenceGateActions(
|
|
249
|
+
readConfidenceJournal({ cwd, slug })
|
|
250
|
+
);
|
|
251
|
+
process.stdout.write(`${JSON.stringify(actions, null, 2)}
|
|
252
|
+
`);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
186
255
|
const confidenceCommand = defineCommand({
|
|
187
256
|
meta: {
|
|
188
257
|
name: "confidence",
|
|
@@ -192,7 +261,8 @@ const confidenceCommand = defineCommand({
|
|
|
192
261
|
log: logCommand,
|
|
193
262
|
read: readCommand,
|
|
194
263
|
summary: summaryCommand,
|
|
195
|
-
render: renderCommand
|
|
264
|
+
render: renderCommand,
|
|
265
|
+
gate: gateCommand
|
|
196
266
|
}
|
|
197
267
|
});
|
|
198
268
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { writeFile, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { LUCA_VERSION } from '../index.mjs';
|
|
5
|
+
import 'citty';
|
|
6
|
+
import 'pathe';
|
|
7
|
+
|
|
8
|
+
const CHECK_NAME = "Config version skew";
|
|
9
|
+
function configPath() {
|
|
10
|
+
return join(process.cwd(), ".luca", "config.json");
|
|
11
|
+
}
|
|
12
|
+
function isDevVersion(v) {
|
|
13
|
+
return v === "0.0.0-dev" || v.includes("dev");
|
|
14
|
+
}
|
|
15
|
+
async function readConfig() {
|
|
16
|
+
const p = configPath();
|
|
17
|
+
if (!existsSync(p)) return null;
|
|
18
|
+
try {
|
|
19
|
+
const raw = JSON.parse(await readFile(p, "utf-8"));
|
|
20
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const configVersionSkewCheck = {
|
|
26
|
+
name: CHECK_NAME,
|
|
27
|
+
scope: "project",
|
|
28
|
+
async run() {
|
|
29
|
+
const pass = (message) => ({
|
|
30
|
+
name: CHECK_NAME,
|
|
31
|
+
status: "pass",
|
|
32
|
+
message,
|
|
33
|
+
fixCommand: null,
|
|
34
|
+
details: null
|
|
35
|
+
});
|
|
36
|
+
if (isDevVersion(LUCA_VERSION)) {
|
|
37
|
+
return pass("dev CLI build \u2014 version skew not checked");
|
|
38
|
+
}
|
|
39
|
+
const config = await readConfig();
|
|
40
|
+
if (config === null) {
|
|
41
|
+
return pass("no .luca/config.json to check");
|
|
42
|
+
}
|
|
43
|
+
const stored = config.lucaVersion;
|
|
44
|
+
if (typeof stored !== "string" || stored.length === 0) {
|
|
45
|
+
return pass("config.json has no lucaVersion to compare");
|
|
46
|
+
}
|
|
47
|
+
if (stored === LUCA_VERSION) {
|
|
48
|
+
return pass(`config.lucaVersion matches CLI (${LUCA_VERSION})`);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: CHECK_NAME,
|
|
52
|
+
status: "warning",
|
|
53
|
+
message: `config.lucaVersion ${stored} \u2260 installed CLI ${LUCA_VERSION}`,
|
|
54
|
+
fixCommand: "luca doctor --fix",
|
|
55
|
+
details: [
|
|
56
|
+
`.luca/config.json records lucaVersion="${stored}" but the installed CLI is ${LUCA_VERSION}.`,
|
|
57
|
+
`Skills/agents that branch on lucaVersion may behave as if on the older version.`,
|
|
58
|
+
`Run \`luca doctor --fix\` to reconcile (rewrites lucaVersion, preserves all other config keys).`
|
|
59
|
+
].join("\n ")
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
async fix() {
|
|
63
|
+
if (isDevVersion(LUCA_VERSION)) {
|
|
64
|
+
return { applied: [], errors: [] };
|
|
65
|
+
}
|
|
66
|
+
const config = await readConfig();
|
|
67
|
+
if (config === null) {
|
|
68
|
+
return { applied: [], errors: [] };
|
|
69
|
+
}
|
|
70
|
+
const stored = config.lucaVersion;
|
|
71
|
+
if (stored === LUCA_VERSION) {
|
|
72
|
+
return { applied: [], errors: [] };
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const next = { ...config, lucaVersion: LUCA_VERSION };
|
|
76
|
+
await writeFile(
|
|
77
|
+
configPath(),
|
|
78
|
+
JSON.stringify(next, null, 2) + "\n"
|
|
79
|
+
);
|
|
80
|
+
return {
|
|
81
|
+
applied: [
|
|
82
|
+
`Reconciled .luca/config.json lucaVersion ${typeof stored === "string" ? stored : "unset"} \u2192 ${LUCA_VERSION}`
|
|
83
|
+
],
|
|
84
|
+
errors: []
|
|
85
|
+
};
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return {
|
|
88
|
+
applied: [],
|
|
89
|
+
errors: [
|
|
90
|
+
`Failed to rewrite .luca/config.json lucaVersion: ${err.message}`
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { configVersionSkewCheck };
|
package/dist/chunks/doctor.mjs
CHANGED
|
@@ -7,16 +7,32 @@ async function executeDoctor(options = {}) {
|
|
|
7
7
|
logger.info("Running environment diagnostics...\n");
|
|
8
8
|
const { bunRuntimeCheck } = await import('./bun-runtime.mjs');
|
|
9
9
|
const { muninndbHealthCheck } = await import('./muninndb-health.mjs');
|
|
10
|
+
const { muninnMcpCheck } = await import('./muninn-mcp.mjs');
|
|
10
11
|
const { staleMcpServerCheck } = await import('./stale-mcp-server.mjs');
|
|
12
|
+
const { staleGlobalSymlinksCheck } = await import('./stale-global-symlinks.mjs');
|
|
13
|
+
const { legacyPackageCheck } = await import('./legacy-package.mjs');
|
|
14
|
+
const { legacyClaudeArtifactsCheck } = await import('./legacy-claude-artifacts.mjs');
|
|
15
|
+
const { sharedTmpPayloadsCheck } = await import('./shared-tmp-payloads.mjs');
|
|
11
16
|
const { strayLocalInstallCheck } = await import('./stray-local-install.mjs');
|
|
17
|
+
const { lucaGitignoreCheck } = await import('./luca-gitignore.mjs');
|
|
18
|
+
const { configVersionSkewCheck } = await import('./config-version-skew.mjs');
|
|
19
|
+
const { vaultConfigLocationCheck } = await import('./vault-config-location.mjs');
|
|
12
20
|
const allChecks = [
|
|
13
21
|
// Prerequisites
|
|
14
22
|
bunRuntimeCheck,
|
|
15
23
|
staleMcpServerCheck,
|
|
16
24
|
// Global
|
|
17
25
|
muninndbHealthCheck,
|
|
26
|
+
muninnMcpCheck,
|
|
27
|
+
staleGlobalSymlinksCheck,
|
|
28
|
+
legacyPackageCheck,
|
|
29
|
+
legacyClaudeArtifactsCheck,
|
|
30
|
+
sharedTmpPayloadsCheck,
|
|
18
31
|
// Project (cwd-dependent)
|
|
19
|
-
strayLocalInstallCheck
|
|
32
|
+
strayLocalInstallCheck,
|
|
33
|
+
configVersionSkewCheck,
|
|
34
|
+
vaultConfigLocationCheck,
|
|
35
|
+
lucaGitignoreCheck
|
|
20
36
|
];
|
|
21
37
|
const checks = scope ? allChecks.filter((check) => check.scope === scope) : allChecks;
|
|
22
38
|
if (checks.length === 0) {
|
package/dist/chunks/hook.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
|
-
import { c as coarsePhaseOf, a as classifyWritePath, A as AUDIT_PATH_PATTERN, W as WAVE_FILE_RE } from '../shared/luca.
|
|
3
|
-
import { l as loadCurrentState, r as resolveActiveSlug } from '../shared/luca.
|
|
2
|
+
import { c as coarsePhaseOf, t as toLucaRelative, a as classifyWritePath, b as TMP_PATH_PATTERN, A as AUDIT_PATH_PATTERN, W as WAVE_FILE_RE } from '../shared/luca.CI5IvdwC.mjs';
|
|
3
|
+
import { l as loadCurrentState, r as resolveActiveSlug } from '../shared/luca.GSXJf0Tv.mjs';
|
|
4
4
|
import 'node:fs';
|
|
5
5
|
import 'node:fs/promises';
|
|
6
6
|
import 'node:path';
|
|
7
|
-
import { S as STEP_ARTIFACTS } from '../shared/luca.
|
|
8
|
-
import { p as phasePathFor } from '../shared/luca.
|
|
7
|
+
import { l as lucaStateClaimOwnerTool, S as STEP_ARTIFACTS } from '../shared/luca.ig-qt2eC.mjs';
|
|
8
|
+
import { p as phasePathFor } from '../shared/luca.DbrDoFFH.mjs';
|
|
9
9
|
import 'node:crypto';
|
|
10
10
|
import 'node:module';
|
|
11
11
|
import 'node:url';
|
|
@@ -72,6 +72,13 @@ function isToolAllowed({
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const READONLY_COMMANDS = /* @__PURE__ */ new Set([
|
|
75
|
+
// Shell navigation/no-ops: mutate shell state, not files — benign for
|
|
76
|
+
// the file/repo-mutation policy this classifier enforces. `cd` is the
|
|
77
|
+
// big one: agents prefix nearly every command with `cd <dir> && …`, so
|
|
78
|
+
// omitting it made every compound command classify as a mutate.
|
|
79
|
+
"cd",
|
|
80
|
+
"pushd",
|
|
81
|
+
"popd",
|
|
75
82
|
"ls",
|
|
76
83
|
"cat",
|
|
77
84
|
"grep",
|
|
@@ -87,7 +94,32 @@ const READONLY_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
87
94
|
"echo",
|
|
88
95
|
"printf",
|
|
89
96
|
"true",
|
|
90
|
-
"false"
|
|
97
|
+
"false",
|
|
98
|
+
// Read-only text filters. These read stdin/files and write to stdout —
|
|
99
|
+
// they never mutate files on their own. Omitting them made any pipeline
|
|
100
|
+
// through a filter (e.g. `find . | sort`) promote to bash-mutate via the
|
|
101
|
+
// unknown-command fallback, blocking read-only inspection in restrictive
|
|
102
|
+
// phases (v13 run report, M1). `tee`/`xargs` are deliberately EXCLUDED:
|
|
103
|
+
// `tee` writes files and `xargs` runs an arbitrary (possibly mutating)
|
|
104
|
+
// command, so they stay conservative.
|
|
105
|
+
"sort",
|
|
106
|
+
"uniq",
|
|
107
|
+
"cut",
|
|
108
|
+
"tr",
|
|
109
|
+
"comm",
|
|
110
|
+
"diff",
|
|
111
|
+
"jq",
|
|
112
|
+
"rg",
|
|
113
|
+
"column",
|
|
114
|
+
"nl",
|
|
115
|
+
"tac",
|
|
116
|
+
"rev",
|
|
117
|
+
"paste",
|
|
118
|
+
"fold",
|
|
119
|
+
"join"
|
|
120
|
+
// NOTE: `playwright-cli` is NOT in this set — it has a dedicated
|
|
121
|
+
// clause (step 4c in classifySubcommand) that extracts `--filename`
|
|
122
|
+
// output paths and enforces the `.playwright-cli/` artifact dir.
|
|
91
123
|
]);
|
|
92
124
|
const GIT_READONLY_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
93
125
|
"status",
|
|
@@ -184,18 +216,29 @@ const PKG_MUTATE_PATTERNS = [
|
|
|
184
216
|
["pnpm", "add"]
|
|
185
217
|
];
|
|
186
218
|
const ALWAYS_DENIED_COMMANDS = /* @__PURE__ */ new Set(["eval", "source", "."]);
|
|
219
|
+
const LUCA_TOPLEVEL_READ = /* @__PURE__ */ new Set(["version", "telemetry", "rules"]);
|
|
220
|
+
const LUCA_TOPLEVEL_WRITE = /* @__PURE__ */ new Set([
|
|
221
|
+
"init",
|
|
222
|
+
"vault:init",
|
|
223
|
+
"retro",
|
|
224
|
+
"claim-verify",
|
|
225
|
+
"classify",
|
|
226
|
+
"doctor",
|
|
227
|
+
"repair"
|
|
228
|
+
]);
|
|
187
229
|
const LUCA_READ_VERBS = /* @__PURE__ */ new Set([
|
|
188
230
|
"read",
|
|
189
231
|
"current",
|
|
190
232
|
"list",
|
|
191
233
|
"guard",
|
|
234
|
+
"aggregate",
|
|
192
235
|
"filter-stale",
|
|
193
236
|
"detect-convergence",
|
|
194
237
|
"regression-check"
|
|
195
238
|
]);
|
|
196
239
|
const LUCA_NOUN_VERBS = {
|
|
197
|
-
state: /* @__PURE__ */ new Set(["read", "advance"]),
|
|
198
|
-
phase: /* @__PURE__ */ new Set(["current"]),
|
|
240
|
+
state: /* @__PURE__ */ new Set(["read", "advance", "claim-owner", "set-current-phase"]),
|
|
241
|
+
phase: /* @__PURE__ */ new Set(["current", "advance", "archive"]),
|
|
199
242
|
roadmap: /* @__PURE__ */ new Set(["read", "create"]),
|
|
200
243
|
preferences: /* @__PURE__ */ new Set(["read", "write"]),
|
|
201
244
|
todo: /* @__PURE__ */ new Set(["add", "list", "update"]),
|
|
@@ -208,13 +251,22 @@ const LUCA_NOUN_VERBS = {
|
|
|
208
251
|
checks: /* @__PURE__ */ new Set(["run"]),
|
|
209
252
|
branch: /* @__PURE__ */ new Set(["guard"]),
|
|
210
253
|
workflow: /* @__PURE__ */ new Set(["reset"]),
|
|
211
|
-
confidence: /* @__PURE__ */ new Set(["log"])
|
|
254
|
+
confidence: /* @__PURE__ */ new Set(["log"]),
|
|
255
|
+
// Read-side surfaces over the per-phase verify.json files.
|
|
256
|
+
verification: /* @__PURE__ */ new Set(["read", "aggregate"])
|
|
212
257
|
};
|
|
213
258
|
function classifyLucaCommand(rest) {
|
|
259
|
+
if (rest.some((t) => ["--help", "-h", "--version"].includes(t))) {
|
|
260
|
+
return "bash-readonly";
|
|
261
|
+
}
|
|
214
262
|
const noun = rest.find((t) => !t.startsWith("-"));
|
|
215
|
-
if (!noun) return
|
|
263
|
+
if (!noun) return "bash-readonly";
|
|
216
264
|
const verbs = LUCA_NOUN_VERBS[noun];
|
|
217
|
-
if (!verbs)
|
|
265
|
+
if (!verbs) {
|
|
266
|
+
if (LUCA_TOPLEVEL_READ.has(noun)) return "bash-readonly";
|
|
267
|
+
if (LUCA_TOPLEVEL_WRITE.has(noun)) return "luca-write";
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
218
270
|
const afterNoun = rest.slice(rest.indexOf(noun) + 1);
|
|
219
271
|
const verb = afterNoun.find((t) => !t.startsWith("-"));
|
|
220
272
|
if (!verb || !verbs.has(verb)) {
|
|
@@ -344,6 +396,20 @@ function classifySubcommand(sub) {
|
|
|
344
396
|
return { category: lucaCategory, targetPaths: [] };
|
|
345
397
|
}
|
|
346
398
|
}
|
|
399
|
+
if (cmd === "playwright-cli") {
|
|
400
|
+
const target = extractFilenameFlag(rest);
|
|
401
|
+
if (target !== void 0) {
|
|
402
|
+
const inArtifactDir = target.startsWith(".playwright-cli/") && !target.split("/").includes("..");
|
|
403
|
+
return {
|
|
404
|
+
category: inArtifactDir && !sub.redirect ? "bash-readonly" : "bash-mutate",
|
|
405
|
+
targetPaths: [...targetsFromRedirect, target]
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
category: sub.redirect ? "bash-mutate" : "bash-readonly",
|
|
410
|
+
targetPaths: targetsFromRedirect
|
|
411
|
+
};
|
|
412
|
+
}
|
|
347
413
|
if (cmd === "bunx" && tokens.includes("tsc") && tokens.includes("--noEmit")) {
|
|
348
414
|
return {
|
|
349
415
|
category: sub.redirect ? "bash-mutate" : "bash-readonly",
|
|
@@ -358,6 +424,16 @@ function classifySubcommand(sub) {
|
|
|
358
424
|
};
|
|
359
425
|
}
|
|
360
426
|
}
|
|
427
|
+
if (cmd === "sed" || cmd === "awk") {
|
|
428
|
+
const sedInPlace = cmd === "sed" && rest.some((a) => a === "--in-place" || a.startsWith("-i"));
|
|
429
|
+
const awkInPlace = cmd === "awk" && rest.some((a, i) => a === "-i" && rest[i + 1] === "inplace");
|
|
430
|
+
if (!sedInPlace && !awkInPlace) {
|
|
431
|
+
return {
|
|
432
|
+
category: sub.redirect ? "bash-mutate" : "bash-readonly",
|
|
433
|
+
targetPaths: targetsFromRedirect
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
361
437
|
if (MUTATE_COMMANDS.has(cmd)) {
|
|
362
438
|
const lastArg = cmd === "cp" || cmd === "mv" || cmd === "ln" ? lastNonFlag(rest) : void 0;
|
|
363
439
|
const sedTarget = cmd === "sed" && rest.includes("-i") ? rest[rest.length - 1] : void 0;
|
|
@@ -380,6 +456,20 @@ function classifySubcommand(sub) {
|
|
|
380
456
|
targetPaths: targetsFromRedirect
|
|
381
457
|
};
|
|
382
458
|
}
|
|
459
|
+
function extractFilenameFlag(args) {
|
|
460
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
461
|
+
const a = args[i];
|
|
462
|
+
if (a.startsWith("--filename=")) {
|
|
463
|
+
const value = a.slice("--filename=".length);
|
|
464
|
+
return value.length > 0 ? value : void 0;
|
|
465
|
+
}
|
|
466
|
+
if (a === "--filename") {
|
|
467
|
+
const value = args[i + 1];
|
|
468
|
+
return value !== void 0 && !value.startsWith("-") ? value : void 0;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return void 0;
|
|
472
|
+
}
|
|
383
473
|
function lastNonFlag(args) {
|
|
384
474
|
for (let i = args.length - 1; i >= 0; i -= 1) {
|
|
385
475
|
if (!args[i].startsWith("-")) return args[i];
|
|
@@ -476,9 +566,21 @@ async function handleStageGateHook(opts) {
|
|
|
476
566
|
}
|
|
477
567
|
const toolName = parsed.tool_name ?? parsed.toolName;
|
|
478
568
|
const toolInput = parsed.tool_input ?? parsed.toolInput;
|
|
569
|
+
const sessionId = parsed.session_id ?? parsed.sessionId;
|
|
479
570
|
const cwd = opts.cwd ?? process.cwd();
|
|
480
571
|
const homedir = opts.homedir ?? process.env.HOME;
|
|
481
572
|
const state = await loadCurrentState({ cwd });
|
|
573
|
+
if (toolName === "Bash" && sessionId && state.ownerSessionId !== sessionId && isStateAdvanceCommand(
|
|
574
|
+
toolInput?.command ?? ""
|
|
575
|
+
)) {
|
|
576
|
+
try {
|
|
577
|
+
await lucaStateClaimOwnerTool.handler({ sessionId }, { cwd });
|
|
578
|
+
state.ownerSessionId = sessionId;
|
|
579
|
+
} catch (err) {
|
|
580
|
+
log(`stage-gate: owner stamp skipped (${err.message})`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const isBystander = Boolean(state.ownerSessionId) && Boolean(sessionId) && state.ownerSessionId !== sessionId;
|
|
482
584
|
const phase = coarsePhaseOf(state.pipelineStep);
|
|
483
585
|
if (phase === "IDLE") {
|
|
484
586
|
log(
|
|
@@ -494,11 +596,12 @@ async function handleStageGateHook(opts) {
|
|
|
494
596
|
log(`stage-gate: ${toolName} without file_path \u2014 allowing`);
|
|
495
597
|
return { exitCode: 0, toolName, toolInput, decision: "allow" };
|
|
496
598
|
}
|
|
497
|
-
const
|
|
599
|
+
const relTarget = toLucaRelative(targetPath, cwd);
|
|
600
|
+
const pc = classifyWritePath(targetPath, { homedir, cwd });
|
|
498
601
|
if (pc.class === "denied") {
|
|
499
602
|
pathBlockReason = `${toolName} to '${targetPath}' is always denied: ${pc.reason ?? "forbidden path"}`;
|
|
500
603
|
} else if (pc.class === "planning-general" || pc.class === "planning-audit") {
|
|
501
|
-
const gate = artifactPathGate(
|
|
604
|
+
const gate = artifactPathGate(relTarget, state.pipelineStep, state);
|
|
502
605
|
if (gate.kind === "block") {
|
|
503
606
|
const msg = `stage-gate BLOCK: ${toolName} ${gate.reason} (pipelineStep=${state.pipelineStep})`;
|
|
504
607
|
log(msg);
|
|
@@ -555,6 +658,12 @@ async function handleStageGateHook(opts) {
|
|
|
555
658
|
log("stage-gate: could not classify tool \u2014 allowing");
|
|
556
659
|
return { exitCode: 0, toolName, toolInput, decision: "allow" };
|
|
557
660
|
}
|
|
661
|
+
if (isBystander) {
|
|
662
|
+
log(
|
|
663
|
+
`stage-gate: session ${sessionId} is not the run owner (${state.ownerSessionId}) \u2014 exempting ${toolName} (category=${category}) from the phase=${phase} matrix`
|
|
664
|
+
);
|
|
665
|
+
return { exitCode: 0, toolName, toolInput, decision: "allow" };
|
|
666
|
+
}
|
|
558
667
|
const allowed = isToolAllowed({ phase, category });
|
|
559
668
|
if (!allowed) {
|
|
560
669
|
const msg = `stage-gate BLOCK: ${toolName} (category=${category}) is not allowed in phase=${phase} (pipelineStep=${state.pipelineStep})`;
|
|
@@ -572,6 +681,22 @@ async function handleStageGateHook(opts) {
|
|
|
572
681
|
);
|
|
573
682
|
return { exitCode: 0, toolName, toolInput, decision: "allow" };
|
|
574
683
|
}
|
|
684
|
+
function isStateAdvanceCommand(command) {
|
|
685
|
+
if (!command.includes("advance")) return false;
|
|
686
|
+
let entries;
|
|
687
|
+
try {
|
|
688
|
+
entries = parse(command);
|
|
689
|
+
} catch {
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
const toks = entries.filter((e) => typeof e === "string");
|
|
693
|
+
for (let i = 0; i <= toks.length - 3; i += 1) {
|
|
694
|
+
if (toks[i] === "luca" && toks[i + 1] === "state" && toks[i + 2] === "advance") {
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
575
700
|
const FIXED_PHASE_FILE_ARTIFACTS = /* @__PURE__ */ new Set([
|
|
576
701
|
"research",
|
|
577
702
|
"context",
|
|
@@ -584,6 +709,9 @@ const FIXED_PHASE_FILE_ARTIFACTS = /* @__PURE__ */ new Set([
|
|
|
584
709
|
"execute/progress"
|
|
585
710
|
]);
|
|
586
711
|
function artifactPathGate(targetPath, pipelineStep, state) {
|
|
712
|
+
if (TMP_PATH_PATTERN.test(targetPath)) {
|
|
713
|
+
return { kind: "allow" };
|
|
714
|
+
}
|
|
587
715
|
const legalArtifacts = STEP_ARTIFACTS[pipelineStep];
|
|
588
716
|
if (legalArtifacts.length === 0) {
|
|
589
717
|
return {
|