muonroi-cli 1.4.1 → 1.5.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/LICENSE +21 -21
- package/README.md +122 -122
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +48 -1
- package/dist/src/agent-harness/mock-model.d.ts +11 -0
- package/dist/src/agent-harness/mock-model.js +21 -0
- package/dist/src/cli/cost-forensics.js +12 -12
- package/dist/src/council/__tests__/clarification-prompt.test.js +51 -0
- package/dist/src/council/__tests__/clarifier-ready-gate.test.js +32 -0
- package/dist/src/council/__tests__/decisions-lock.test.js +17 -1
- package/dist/src/council/__tests__/oauth-reachable.test.d.ts +1 -0
- package/dist/src/council/__tests__/oauth-reachable.test.js +31 -0
- package/dist/src/council/__tests__/parse-outcome-fallback.test.js +11 -0
- package/dist/src/council/clarifier.js +9 -1
- package/dist/src/council/debate.js +5 -1
- package/dist/src/council/decisions-lock.js +3 -3
- package/dist/src/council/index.js +12 -5
- package/dist/src/council/leader.d.ts +0 -17
- package/dist/src/council/leader.js +22 -15
- package/dist/src/council/planner.js +1 -1
- package/dist/src/council/prompts.js +63 -57
- package/dist/src/council/types.d.ts +7 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.d.ts +1 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.js +32 -0
- package/dist/src/ee/auth.d.ts +9 -0
- package/dist/src/ee/auth.js +19 -0
- package/dist/src/ee/ee-onboarding.d.ts +5 -0
- package/dist/src/ee/ee-onboarding.js +76 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/output.js +6 -4
- package/dist/src/headless/output.test.js +4 -3
- package/dist/src/index.js +20 -1
- package/dist/src/mcp/__tests__/auto-setup.test.js +74 -0
- package/dist/src/mcp/__tests__/client-pool.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/client-pool.spec.js +98 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.js +67 -0
- package/dist/src/mcp/__tests__/smart-filter.test.js +56 -0
- package/dist/src/mcp/auto-setup.js +56 -2
- package/dist/src/mcp/client-pool.d.ts +46 -0
- package/dist/src/mcp/client-pool.js +212 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/runtime.d.ts +28 -0
- package/dist/src/mcp/runtime.js +117 -51
- package/dist/src/mcp/self-verify-runner.d.ts +14 -0
- package/dist/src/mcp/self-verify-runner.js +38 -0
- package/dist/src/mcp/setup-guide-text.d.ts +9 -0
- package/dist/src/mcp/setup-guide-text.js +84 -0
- package/dist/src/mcp/smart-filter.js +49 -0
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/mcp/tools-server.d.ts +7 -0
- package/dist/src/mcp/tools-server.js +19 -22
- package/dist/src/models/catalog.json +349 -349
- package/dist/src/ops/__tests__/doctor-ee-health.test.js +21 -0
- package/dist/src/ops/doctor.d.ts +3 -2
- package/dist/src/ops/doctor.js +47 -11
- package/dist/src/ops/doctor.test.js +4 -3
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.js +39 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.js +65 -0
- package/dist/src/orchestrator/batch-turn-runner.js +7 -11
- package/dist/src/orchestrator/message-processor.js +57 -27
- package/dist/src/orchestrator/orchestrator.js +26 -0
- package/dist/src/orchestrator/prompts.d.ts +51 -0
- package/dist/src/orchestrator/prompts.js +257 -134
- package/dist/src/orchestrator/scope-ceiling.js +6 -1
- package/dist/src/orchestrator/stream-runner.js +20 -15
- package/dist/src/orchestrator/text-tool-call-detector.test.js +13 -13
- package/dist/src/pil/__tests__/clarity-gate.test.js +24 -215
- package/dist/src/pil/__tests__/config.test.js +1 -17
- package/dist/src/pil/__tests__/discovery.test.js +144 -11
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +7 -2
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +32 -116
- package/dist/src/pil/__tests__/layer4-gsd.test.js +37 -0
- package/dist/src/pil/__tests__/layer6-output.test.js +137 -18
- package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +2 -0
- package/dist/src/pil/agent-operating-contract.test.js +7 -2
- package/dist/src/pil/cheap-model-playbook.js +35 -35
- package/dist/src/pil/cheap-model-workbooks.js +16 -13
- package/dist/src/pil/clarity-gate.d.ts +21 -19
- package/dist/src/pil/clarity-gate.js +26 -153
- package/dist/src/pil/config.d.ts +9 -1
- package/dist/src/pil/config.js +15 -4
- package/dist/src/pil/discovery.js +211 -136
- package/dist/src/pil/layer1-intent.d.ts +12 -0
- package/dist/src/pil/layer1-intent.js +283 -38
- package/dist/src/pil/layer1-intent.test.js +210 -4
- package/dist/src/pil/layer16-clarity.d.ts +25 -11
- package/dist/src/pil/layer16-clarity.js +19 -306
- package/dist/src/pil/layer4-gsd.js +18 -6
- package/dist/src/pil/layer6-output.d.ts +2 -0
- package/dist/src/pil/layer6-output.js +137 -22
- package/dist/src/pil/llm-classify.d.ts +26 -0
- package/dist/src/pil/llm-classify.js +34 -5
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +82 -76
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +12 -1
- package/dist/src/pil/task-tier-map.js +4 -0
- package/dist/src/pil/types.d.ts +11 -1
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/loop-driver.js +18 -18
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/providers/auth/gemini-oauth.js +6 -15
- package/dist/src/providers/auth/grok-oauth.js +6 -15
- package/dist/src/providers/auth/openai-oauth.js +6 -15
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +24 -19
- package/dist/src/self-qa/spec-emitter.js +26 -23
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +122 -122
- package/dist/src/storage/sessions.js +42 -42
- package/dist/src/storage/transcript.js +91 -84
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/__tests__/native-tools.test.d.ts +1 -0
- package/dist/src/tools/__tests__/native-tools.test.js +53 -0
- package/dist/src/tools/git-safety.d.ts +61 -0
- package/dist/src/tools/git-safety.js +141 -0
- package/dist/src/tools/git-safety.test.d.ts +1 -0
- package/dist/src/tools/git-safety.test.js +111 -0
- package/dist/src/tools/native-tools.d.ts +31 -0
- package/dist/src/tools/native-tools.js +273 -0
- package/dist/src/tools/registry-git-safety.test.d.ts +7 -0
- package/dist/src/tools/registry-git-safety.test.js +92 -0
- package/dist/src/tools/registry.js +39 -4
- package/dist/src/ui/__tests__/markdown-render.test.d.ts +1 -0
- package/dist/src/ui/__tests__/markdown-render.test.js +48 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/message-view.js +4 -1
- package/dist/src/ui/components/structured-response-view.js +7 -3
- package/dist/src/ui/components/tool-group.js +7 -1
- package/dist/src/ui/markdown-render.d.ts +41 -0
- package/dist/src/ui/markdown-render.js +223 -0
- package/dist/src/ui/markdown.d.ts +10 -0
- package/dist/src/ui/markdown.js +12 -35
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/export.js +4 -4
- package/dist/src/ui/utils/text.d.ts +8 -0
- package/dist/src/ui/utils/text.js +16 -0
- package/dist/src/ui/utils/text.test.d.ts +1 -0
- package/dist/src/ui/utils/text.test.js +23 -0
- package/dist/src/usage/ledger.js +48 -15
- package/dist/src/utils/__tests__/footprint-gitignore.test.d.ts +1 -0
- package/dist/src/utils/__tests__/footprint-gitignore.test.js +50 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/open-url.d.ts +56 -0
- package/dist/src/utils/open-url.js +58 -0
- package/dist/src/utils/open-url.test.d.ts +1 -0
- package/dist/src/utils/open-url.test.js +86 -0
- package/dist/src/utils/settings.d.ts +12 -0
- package/dist/src/utils/settings.js +48 -0
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/dist/src/verify/environment.js +2 -1
- package/package.json +1 -1
- package/dist/src/pil/layer16-clarity.test.js +0 -31
- /package/dist/src/{pil/layer16-clarity.test.d.ts → council/__tests__/clarification-prompt.test.d.ts} +0 -0
|
@@ -42,11 +42,11 @@ function isInternalCouncilMarker(content) {
|
|
|
42
42
|
}
|
|
43
43
|
function loadMessageRows(sessionId) {
|
|
44
44
|
const rows = getDatabase()
|
|
45
|
-
.prepare(`
|
|
46
|
-
SELECT session_id, seq, role, message_json, created_at
|
|
47
|
-
FROM messages
|
|
48
|
-
WHERE session_id = ?
|
|
49
|
-
ORDER BY seq ASC
|
|
45
|
+
.prepare(`
|
|
46
|
+
SELECT session_id, seq, role, message_json, created_at
|
|
47
|
+
FROM messages
|
|
48
|
+
WHERE session_id = ?
|
|
49
|
+
ORDER BY seq ASC
|
|
50
50
|
`)
|
|
51
51
|
.all(sessionId);
|
|
52
52
|
for (const row of rows) {
|
|
@@ -66,12 +66,12 @@ function toPersistedCompaction(row) {
|
|
|
66
66
|
}
|
|
67
67
|
export function loadLatestCompaction(sessionId) {
|
|
68
68
|
const row = getDatabase()
|
|
69
|
-
.prepare(`
|
|
70
|
-
SELECT session_id, first_kept_seq, summary, tokens_before, created_at
|
|
71
|
-
FROM compactions
|
|
72
|
-
WHERE session_id = ?
|
|
73
|
-
ORDER BY id DESC
|
|
74
|
-
LIMIT 1
|
|
69
|
+
.prepare(`
|
|
70
|
+
SELECT session_id, first_kept_seq, summary, tokens_before, created_at
|
|
71
|
+
FROM compactions
|
|
72
|
+
WHERE session_id = ?
|
|
73
|
+
ORDER BY id DESC
|
|
74
|
+
LIMIT 1
|
|
75
75
|
`)
|
|
76
76
|
.get(sessionId);
|
|
77
77
|
return toPersistedCompaction(row);
|
|
@@ -112,37 +112,37 @@ export function appendMessages(sessionId, messages) {
|
|
|
112
112
|
// with `status='completed'` and the full message_json. Pre-A5 callers
|
|
113
113
|
// hit this path identically: there is no pending row so the IGNORE
|
|
114
114
|
// branch is unused and the INSERT wins.
|
|
115
|
-
const insertMessage = db.prepare(`
|
|
116
|
-
INSERT INTO messages (session_id, seq, role, message_json, created_at, status)
|
|
117
|
-
VALUES (?, ?, ?, ?, ?, 'completed')
|
|
118
|
-
ON CONFLICT(session_id, seq) DO UPDATE SET
|
|
119
|
-
role = excluded.role,
|
|
120
|
-
message_json = excluded.message_json,
|
|
121
|
-
status = 'completed'
|
|
115
|
+
const insertMessage = db.prepare(`
|
|
116
|
+
INSERT INTO messages (session_id, seq, role, message_json, created_at, status)
|
|
117
|
+
VALUES (?, ?, ?, ?, ?, 'completed')
|
|
118
|
+
ON CONFLICT(session_id, seq) DO UPDATE SET
|
|
119
|
+
role = excluded.role,
|
|
120
|
+
message_json = excluded.message_json,
|
|
121
|
+
status = 'completed'
|
|
122
122
|
`);
|
|
123
|
-
const insertToolCall = db.prepare(`
|
|
124
|
-
INSERT OR IGNORE INTO tool_calls (
|
|
125
|
-
session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
|
|
126
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
123
|
+
const insertToolCall = db.prepare(`
|
|
124
|
+
INSERT OR IGNORE INTO tool_calls (
|
|
125
|
+
session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
|
|
126
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
127
127
|
`);
|
|
128
|
-
const updateToolCall = db.prepare(`
|
|
129
|
-
UPDATE tool_calls
|
|
130
|
-
SET tool_name = ?, args_json = ?, status = ?, completed_at = ?
|
|
131
|
-
WHERE session_id = ? AND tool_call_id = ?
|
|
128
|
+
const updateToolCall = db.prepare(`
|
|
129
|
+
UPDATE tool_calls
|
|
130
|
+
SET tool_name = ?, args_json = ?, status = ?, completed_at = ?
|
|
131
|
+
WHERE session_id = ? AND tool_call_id = ?
|
|
132
132
|
`);
|
|
133
|
-
const selectToolCall = db.prepare(`
|
|
134
|
-
SELECT id, tool_call_id, tool_name, args_json
|
|
135
|
-
FROM tool_calls
|
|
136
|
-
WHERE session_id = ? AND tool_call_id = ?
|
|
133
|
+
const selectToolCall = db.prepare(`
|
|
134
|
+
SELECT id, tool_call_id, tool_name, args_json
|
|
135
|
+
FROM tool_calls
|
|
136
|
+
WHERE session_id = ? AND tool_call_id = ?
|
|
137
137
|
`);
|
|
138
|
-
const insertToolResult = db.prepare(`
|
|
139
|
-
INSERT INTO tool_results (tool_call_row_id, output_kind, output_json, success, created_at)
|
|
140
|
-
VALUES (?, ?, ?, ?, ?)
|
|
138
|
+
const insertToolResult = db.prepare(`
|
|
139
|
+
INSERT INTO tool_results (tool_call_row_id, output_kind, output_json, success, created_at)
|
|
140
|
+
VALUES (?, ?, ?, ?, ?)
|
|
141
141
|
`);
|
|
142
|
-
const updateSession = db.prepare(`
|
|
143
|
-
UPDATE sessions
|
|
144
|
-
SET updated_at = ?
|
|
145
|
-
WHERE id = ?
|
|
142
|
+
const updateSession = db.prepare(`
|
|
143
|
+
UPDATE sessions
|
|
144
|
+
SET updated_at = ?
|
|
145
|
+
WHERE id = ?
|
|
146
146
|
`);
|
|
147
147
|
messages.forEach((message, index) => {
|
|
148
148
|
const seq = nextSeq + index;
|
|
@@ -204,10 +204,10 @@ export function persistToolCallWriteAhead(sessionId, messageSeq, toolCallId, too
|
|
|
204
204
|
const now = new Date().toISOString();
|
|
205
205
|
try {
|
|
206
206
|
getDatabase()
|
|
207
|
-
.prepare(`
|
|
208
|
-
INSERT OR IGNORE INTO tool_calls (
|
|
209
|
-
session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
|
|
210
|
-
) VALUES (?, ?, ?, ?, ?, 'pending', ?, NULL)
|
|
207
|
+
.prepare(`
|
|
208
|
+
INSERT OR IGNORE INTO tool_calls (
|
|
209
|
+
session_id, message_seq, tool_call_id, tool_name, args_json, status, started_at, completed_at
|
|
210
|
+
) VALUES (?, ?, ?, ?, ?, 'pending', ?, NULL)
|
|
211
211
|
`)
|
|
212
212
|
.run(sessionId, messageSeq, toolCallId, toolName, argsJson, now);
|
|
213
213
|
}
|
|
@@ -253,10 +253,10 @@ export function persistMessageWriteAhead(sessionId, seq, role, messageJson) {
|
|
|
253
253
|
const now = new Date().toISOString();
|
|
254
254
|
try {
|
|
255
255
|
getDatabase()
|
|
256
|
-
.prepare(`
|
|
257
|
-
INSERT OR IGNORE INTO messages (
|
|
258
|
-
session_id, seq, role, message_json, created_at, status
|
|
259
|
-
) VALUES (?, ?, ?, ?, ?, 'pending')
|
|
256
|
+
.prepare(`
|
|
257
|
+
INSERT OR IGNORE INTO messages (
|
|
258
|
+
session_id, seq, role, message_json, created_at, status
|
|
259
|
+
) VALUES (?, ?, ?, ?, ?, 'pending')
|
|
260
260
|
`)
|
|
261
261
|
.run(sessionId, seq, role, messageJson, now);
|
|
262
262
|
}
|
|
@@ -274,10 +274,10 @@ export function persistMessageWriteAhead(sessionId, seq, role, messageJson) {
|
|
|
274
274
|
export function markMessageCompleted(sessionId, seq) {
|
|
275
275
|
try {
|
|
276
276
|
getDatabase()
|
|
277
|
-
.prepare(`
|
|
278
|
-
UPDATE messages
|
|
279
|
-
SET status = 'completed'
|
|
280
|
-
WHERE session_id = ? AND seq = ? AND status = 'pending'
|
|
277
|
+
.prepare(`
|
|
278
|
+
UPDATE messages
|
|
279
|
+
SET status = 'completed'
|
|
280
|
+
WHERE session_id = ? AND seq = ? AND status = 'pending'
|
|
281
281
|
`)
|
|
282
282
|
.run(sessionId, seq);
|
|
283
283
|
}
|
|
@@ -296,10 +296,10 @@ export function markMessageCompleted(sessionId, seq) {
|
|
|
296
296
|
export function markMessageErrored(sessionId, seq) {
|
|
297
297
|
try {
|
|
298
298
|
getDatabase()
|
|
299
|
-
.prepare(`
|
|
300
|
-
UPDATE messages
|
|
301
|
-
SET status = 'errored'
|
|
302
|
-
WHERE session_id = ? AND seq = ? AND status = 'pending'
|
|
299
|
+
.prepare(`
|
|
300
|
+
UPDATE messages
|
|
301
|
+
SET status = 'errored'
|
|
302
|
+
WHERE session_id = ? AND seq = ? AND status = 'pending'
|
|
303
303
|
`)
|
|
304
304
|
.run(sessionId, seq);
|
|
305
305
|
}
|
|
@@ -331,17 +331,17 @@ export function sweepStalePendingRows(staleAfterMs = 5 * 60 * 1000) {
|
|
|
331
331
|
try {
|
|
332
332
|
const db = getDatabase();
|
|
333
333
|
const toolCalls = db
|
|
334
|
-
.prepare(`
|
|
335
|
-
UPDATE tool_calls
|
|
336
|
-
SET status = 'aborted', completed_at = ?
|
|
337
|
-
WHERE status = 'pending' AND started_at < ?
|
|
334
|
+
.prepare(`
|
|
335
|
+
UPDATE tool_calls
|
|
336
|
+
SET status = 'aborted', completed_at = ?
|
|
337
|
+
WHERE status = 'pending' AND started_at < ?
|
|
338
338
|
`)
|
|
339
339
|
.run(now, cutoff);
|
|
340
340
|
const messages = db
|
|
341
|
-
.prepare(`
|
|
342
|
-
UPDATE messages
|
|
343
|
-
SET status = 'aborted'
|
|
344
|
-
WHERE status = 'pending' AND created_at < ?
|
|
341
|
+
.prepare(`
|
|
342
|
+
UPDATE messages
|
|
343
|
+
SET status = 'aborted'
|
|
344
|
+
WHERE status = 'pending' AND created_at < ?
|
|
345
345
|
`)
|
|
346
346
|
.run(cutoff);
|
|
347
347
|
return { toolCalls: toolCalls.changes, messages: messages.changes };
|
|
@@ -355,10 +355,10 @@ export function markToolCallErrored(sessionId, toolCallId, errorMessage) {
|
|
|
355
355
|
const now = new Date().toISOString();
|
|
356
356
|
try {
|
|
357
357
|
getDatabase()
|
|
358
|
-
.prepare(`
|
|
359
|
-
UPDATE tool_calls
|
|
360
|
-
SET status = 'errored', completed_at = ?, args_json = COALESCE(args_json, ?)
|
|
361
|
-
WHERE session_id = ? AND tool_call_id = ?
|
|
358
|
+
.prepare(`
|
|
359
|
+
UPDATE tool_calls
|
|
360
|
+
SET status = 'errored', completed_at = ?, args_json = COALESCE(args_json, ?)
|
|
361
|
+
WHERE session_id = ? AND tool_call_id = ?
|
|
362
362
|
`)
|
|
363
363
|
.run(now, JSON.stringify({ error: errorMessage.slice(0, 500) }), sessionId, toolCallId);
|
|
364
364
|
}
|
|
@@ -368,14 +368,14 @@ export function markToolCallErrored(sessionId, toolCallId, errorMessage) {
|
|
|
368
368
|
}
|
|
369
369
|
export function appendCompaction(sessionId, firstKeptSeq, summary, tokensBefore) {
|
|
370
370
|
withTransaction((db) => {
|
|
371
|
-
db.prepare(`
|
|
372
|
-
INSERT INTO compactions (session_id, first_kept_seq, summary, tokens_before, created_at)
|
|
373
|
-
VALUES (?, ?, ?, ?, ?)
|
|
371
|
+
db.prepare(`
|
|
372
|
+
INSERT INTO compactions (session_id, first_kept_seq, summary, tokens_before, created_at)
|
|
373
|
+
VALUES (?, ?, ?, ?, ?)
|
|
374
374
|
`).run(sessionId, firstKeptSeq, summary, tokensBefore, new Date().toISOString());
|
|
375
|
-
db.prepare(`
|
|
376
|
-
UPDATE sessions
|
|
377
|
-
SET updated_at = ?
|
|
378
|
-
WHERE id = ?
|
|
375
|
+
db.prepare(`
|
|
376
|
+
UPDATE sessions
|
|
377
|
+
SET updated_at = ?
|
|
378
|
+
WHERE id = ?
|
|
379
379
|
`).run(new Date().toISOString(), sessionId);
|
|
380
380
|
});
|
|
381
381
|
}
|
|
@@ -393,9 +393,16 @@ export function buildChatEntries(sessionId) {
|
|
|
393
393
|
continue;
|
|
394
394
|
}
|
|
395
395
|
if (message.role === "system") {
|
|
396
|
-
const
|
|
396
|
+
const summaryText = getCompactionSummaryText(message);
|
|
397
|
+
const content = summaryText ?? (typeof message.content === "string" ? message.content.trim() : "");
|
|
397
398
|
if (content && !isInternalCouncilMarker(content)) {
|
|
398
|
-
|
|
399
|
+
// A compaction checkpoint is internal context-management, NOT the
|
|
400
|
+
// assistant's answer. Tag it with a source label so the UI renders it
|
|
401
|
+
// as a clearly-marked, collapsible checkpoint instead of dumping the
|
|
402
|
+
// raw "## Goal / ## Context For Suffix" scaffolding as a chat reply.
|
|
403
|
+
entries.push(summaryText !== null
|
|
404
|
+
? { type: "assistant", content, timestamp, sourceLabel: "⋯ context checkpoint (auto-compacted)" }
|
|
405
|
+
: { type: "assistant", content, timestamp });
|
|
399
406
|
}
|
|
400
407
|
continue;
|
|
401
408
|
}
|
|
@@ -460,10 +467,10 @@ export function buildChatEntries(sessionId) {
|
|
|
460
467
|
}
|
|
461
468
|
function getNextSequence(db, sessionId) {
|
|
462
469
|
const row = db
|
|
463
|
-
.prepare(`
|
|
464
|
-
SELECT COALESCE(MAX(seq), 0) AS max_seq
|
|
465
|
-
FROM messages
|
|
466
|
-
WHERE session_id = ?
|
|
470
|
+
.prepare(`
|
|
471
|
+
SELECT COALESCE(MAX(seq), 0) AS max_seq
|
|
472
|
+
FROM messages
|
|
473
|
+
WHERE session_id = ?
|
|
467
474
|
`)
|
|
468
475
|
.get(sessionId);
|
|
469
476
|
return (row?.max_seq ?? 0) + 1;
|
|
@@ -513,12 +520,12 @@ function renderAssistantContent(content, callMap) {
|
|
|
513
520
|
}
|
|
514
521
|
function loadStoredToolResults(sessionId) {
|
|
515
522
|
const rows = getDatabase()
|
|
516
|
-
.prepare(`
|
|
517
|
-
SELECT tc.tool_call_id, tr.output_json
|
|
518
|
-
FROM tool_results tr
|
|
519
|
-
JOIN tool_calls tc ON tc.id = tr.tool_call_row_id
|
|
520
|
-
WHERE tc.session_id = ?
|
|
521
|
-
ORDER BY tr.id ASC
|
|
523
|
+
.prepare(`
|
|
524
|
+
SELECT tc.tool_call_id, tr.output_json
|
|
525
|
+
FROM tool_results tr
|
|
526
|
+
JOIN tool_calls tc ON tc.id = tr.tool_call_row_id
|
|
527
|
+
WHERE tc.session_id = ?
|
|
528
|
+
ORDER BY tr.id ASC
|
|
522
529
|
`)
|
|
523
530
|
.all(sessionId);
|
|
524
531
|
return new Map(rows.map((row) => [row.tool_call_id, JSON.parse(row.output_json)]));
|
|
@@ -12,31 +12,31 @@ export function recordUsageEvent(sessionId, source, model, usage, messageSeq, pi
|
|
|
12
12
|
const cacheCreationTokens = usage.cacheCreationTokens ?? 0;
|
|
13
13
|
const costMicros = estimateCostMicros(model, inputTokens, outputTokens, cacheReadTokens);
|
|
14
14
|
getDatabase()
|
|
15
|
-
.prepare(`
|
|
16
|
-
INSERT INTO usage_events (
|
|
17
|
-
session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at,
|
|
18
|
-
pil_active, enrichment_delta, cache_read_tokens, cache_creation_tokens, provider_options_shape
|
|
19
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
15
|
+
.prepare(`
|
|
16
|
+
INSERT INTO usage_events (
|
|
17
|
+
session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at,
|
|
18
|
+
pil_active, enrichment_delta, cache_read_tokens, cache_creation_tokens, provider_options_shape
|
|
19
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
20
20
|
`)
|
|
21
21
|
.run(sessionId, messageSeq ?? null, source, model, inputTokens, outputTokens, totalTokens, costMicros, new Date().toISOString(), pilActive ? 1 : 0, enrichmentDelta, cacheReadTokens, cacheCreationTokens, providerOptionsShape);
|
|
22
22
|
}
|
|
23
23
|
export function getSessionTotalTokens(sessionId) {
|
|
24
24
|
const row = getDatabase()
|
|
25
|
-
.prepare(`
|
|
26
|
-
SELECT COALESCE(SUM(total_tokens), 0) AS total_tokens
|
|
27
|
-
FROM usage_events
|
|
28
|
-
WHERE session_id = ?
|
|
25
|
+
.prepare(`
|
|
26
|
+
SELECT COALESCE(SUM(total_tokens), 0) AS total_tokens
|
|
27
|
+
FROM usage_events
|
|
28
|
+
WHERE session_id = ?
|
|
29
29
|
`)
|
|
30
30
|
.get(sessionId);
|
|
31
31
|
return row?.total_tokens ?? 0;
|
|
32
32
|
}
|
|
33
33
|
export function listSessionUsage(sessionId) {
|
|
34
34
|
const rows = getDatabase()
|
|
35
|
-
.prepare(`
|
|
36
|
-
SELECT id, session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at, cache_read_tokens, cache_creation_tokens
|
|
37
|
-
FROM usage_events
|
|
38
|
-
WHERE session_id = ?
|
|
39
|
-
ORDER BY id ASC
|
|
35
|
+
.prepare(`
|
|
36
|
+
SELECT id, session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at, cache_read_tokens, cache_creation_tokens
|
|
37
|
+
FROM usage_events
|
|
38
|
+
WHERE session_id = ?
|
|
39
|
+
ORDER BY id ASC
|
|
40
40
|
`)
|
|
41
41
|
.all(sessionId);
|
|
42
42
|
return rows.map((row) => ({
|
|
@@ -8,14 +8,14 @@ export function ensureWorkspace(cwd) {
|
|
|
8
8
|
const now = new Date().toISOString();
|
|
9
9
|
const id = createHash("sha1").update(resolved.scopeKey).digest("hex").slice(0, 16);
|
|
10
10
|
const db = getDatabase();
|
|
11
|
-
db.prepare(`
|
|
12
|
-
INSERT INTO workspaces (id, scope_key, canonical_path, git_root, display_name, last_seen_at)
|
|
13
|
-
VALUES (@id, @scope_key, @canonical_path, @git_root, @display_name, @last_seen_at)
|
|
14
|
-
ON CONFLICT(scope_key) DO UPDATE SET
|
|
15
|
-
canonical_path = excluded.canonical_path,
|
|
16
|
-
git_root = excluded.git_root,
|
|
17
|
-
display_name = excluded.display_name,
|
|
18
|
-
last_seen_at = excluded.last_seen_at
|
|
11
|
+
db.prepare(`
|
|
12
|
+
INSERT INTO workspaces (id, scope_key, canonical_path, git_root, display_name, last_seen_at)
|
|
13
|
+
VALUES (@id, @scope_key, @canonical_path, @git_root, @display_name, @last_seen_at)
|
|
14
|
+
ON CONFLICT(scope_key) DO UPDATE SET
|
|
15
|
+
canonical_path = excluded.canonical_path,
|
|
16
|
+
git_root = excluded.git_root,
|
|
17
|
+
display_name = excluded.display_name,
|
|
18
|
+
last_seen_at = excluded.last_seen_at
|
|
19
19
|
`).run({
|
|
20
20
|
id,
|
|
21
21
|
scope_key: resolved.scopeKey,
|
|
@@ -25,10 +25,10 @@ export function ensureWorkspace(cwd) {
|
|
|
25
25
|
last_seen_at: now,
|
|
26
26
|
});
|
|
27
27
|
const row = db
|
|
28
|
-
.prepare(`
|
|
29
|
-
SELECT id, scope_key, canonical_path, git_root, display_name, last_seen_at
|
|
30
|
-
FROM workspaces
|
|
31
|
-
WHERE scope_key = ?
|
|
28
|
+
.prepare(`
|
|
29
|
+
SELECT id, scope_key, canonical_path, git_root, display_name, last_seen_at
|
|
30
|
+
FROM workspaces
|
|
31
|
+
WHERE scope_key = ?
|
|
32
32
|
`)
|
|
33
33
|
.get(resolved.scopeKey);
|
|
34
34
|
if (!row) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { NATIVE_MUONROI_TOOL_NAMES, registerNativeMuonroiTools } from "../native-tools.js";
|
|
3
|
+
async function exec(tools, name, args) {
|
|
4
|
+
const tool = tools[name];
|
|
5
|
+
return (await tool.execute(args, {}));
|
|
6
|
+
}
|
|
7
|
+
describe("registerNativeMuonroiTools", () => {
|
|
8
|
+
it("registers every muonroi-tools capability as a native builtin", () => {
|
|
9
|
+
const tools = {};
|
|
10
|
+
registerNativeMuonroiTools(tools);
|
|
11
|
+
for (const name of NATIVE_MUONROI_TOOL_NAMES) {
|
|
12
|
+
expect(tools[name], `missing native tool ${name}`).toBeDefined();
|
|
13
|
+
expect(typeof tools[name].execute).toBe("function");
|
|
14
|
+
}
|
|
15
|
+
// ee_query stays the registry's own native tool — not duplicated here.
|
|
16
|
+
expect(tools.ee_query).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
it("setup_guide returns the shared guide text (no MCP round-trip)", async () => {
|
|
19
|
+
const tools = {};
|
|
20
|
+
registerNativeMuonroiTools(tools);
|
|
21
|
+
const out = await exec(tools, "setup_guide", {});
|
|
22
|
+
expect(out).toContain("muonroi-cli Setup Guide");
|
|
23
|
+
expect(out).toContain("muonroi-cli doctor");
|
|
24
|
+
});
|
|
25
|
+
it("ee_feedback rejects a noise verdict with no reason (no network call)", async () => {
|
|
26
|
+
const tools = {};
|
|
27
|
+
registerNativeMuonroiTools(tools);
|
|
28
|
+
const out = await exec(tools, "ee_feedback", { id: "abc", collection: "experience-behavioral", verdict: "noise" });
|
|
29
|
+
expect(out).toMatch(/reason_required/);
|
|
30
|
+
});
|
|
31
|
+
it("ee_feedback validates required args", async () => {
|
|
32
|
+
const tools = {};
|
|
33
|
+
registerNativeMuonroiTools(tools);
|
|
34
|
+
expect(await exec(tools, "ee_feedback", { verdict: "followed" })).toMatch(/invalid_args/);
|
|
35
|
+
});
|
|
36
|
+
it("selfverify_status returns not_found for an unknown runId (shared JobManager)", async () => {
|
|
37
|
+
const tools = {};
|
|
38
|
+
registerNativeMuonroiTools(tools);
|
|
39
|
+
const out = await exec(tools, "selfverify_status", { runId: "does-not-exist" });
|
|
40
|
+
expect(out).toMatch(/not_found/);
|
|
41
|
+
});
|
|
42
|
+
it("selfverify_start rejects agentic mode without goal+llm", async () => {
|
|
43
|
+
const tools = {};
|
|
44
|
+
registerNativeMuonroiTools(tools);
|
|
45
|
+
expect(await exec(tools, "selfverify_start", { mode: "agentic" })).toMatch(/invalid_args/);
|
|
46
|
+
});
|
|
47
|
+
it("usage_forensics rejects an empty prefix without touching the DB", async () => {
|
|
48
|
+
const tools = {};
|
|
49
|
+
registerNativeMuonroiTools(tools);
|
|
50
|
+
expect(await exec(tools, "usage_forensics", { prefix: " " })).toMatch(/invalid_args/);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=native-tools.test.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/git-safety.ts
|
|
3
|
+
*
|
|
4
|
+
* Pre-execution git safety for the bash tool. Distilled from a real session
|
|
5
|
+
* audit (18285908637a) where a cheap model:
|
|
6
|
+
* 1. pushed while 24 tests were failing (it batched `git add -A && commit &&
|
|
7
|
+
* push` in the SAME tool batch as the test run, so push was never gated
|
|
8
|
+
* on the result), and
|
|
9
|
+
* 2. swept the CLI's own `.muonroi-cli/settings.json` (which can hold
|
|
10
|
+
* provider API keys) into a public repo via `git add -A`.
|
|
11
|
+
*
|
|
12
|
+
* Two guards, both cheap and side-effect-free:
|
|
13
|
+
* - PUSH GATE: a session-scoped record of failed verification commands
|
|
14
|
+
* (test/build/lint/typecheck). A `git push` is BLOCKED (not executed)
|
|
15
|
+
* while any verification command has failed this session and not been
|
|
16
|
+
* re-run green. Mirrors the repo's mandatory Pre-Push Test Gate.
|
|
17
|
+
* - STAGING WARNING: a non-blocking warning when a broad `git add -A` /
|
|
18
|
+
* `git add .` / `git commit -a` is run while sensitive paths (`.env`,
|
|
19
|
+
* `.muonroi-cli/`, private keys, credentials) exist in the repo root.
|
|
20
|
+
*
|
|
21
|
+
* The push gate is deterministic for the sequential case (a failed check then
|
|
22
|
+
* a later push). The concurrent case (push batched in the same parallel tool
|
|
23
|
+
* call as the check) is covered by the system-prompt git-safety clause.
|
|
24
|
+
*
|
|
25
|
+
* Override the push gate with `MUONROI_ALLOW_PUSH_ON_RED=1`.
|
|
26
|
+
*/
|
|
27
|
+
interface GitSafetyEntry {
|
|
28
|
+
/** canonical verification command -> epoch ms it last failed. */
|
|
29
|
+
failedVerifies: Map<string, number>;
|
|
30
|
+
}
|
|
31
|
+
declare global {
|
|
32
|
+
var __muonroiGitSafetyState: Map<string, GitSafetyEntry> | undefined;
|
|
33
|
+
}
|
|
34
|
+
/** Test helper — clear all session git-safety state. */
|
|
35
|
+
export declare function __resetGitSafetyState(): void;
|
|
36
|
+
export interface GitCommandShape {
|
|
37
|
+
isPush: boolean;
|
|
38
|
+
/** `git add -A` / `git add .` / `git add --all` / `git commit -a[m]`. */
|
|
39
|
+
isBroadStage: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function analyzeGitCommand(command: string): GitCommandShape;
|
|
42
|
+
/**
|
|
43
|
+
* Record the outcome of a bash command for the push gate. Only verification
|
|
44
|
+
* commands (test/build/lint/typecheck) are tracked. A pass clears that exact
|
|
45
|
+
* command's failed flag; a failure sets it. Non-verification commands are
|
|
46
|
+
* ignored.
|
|
47
|
+
*/
|
|
48
|
+
export declare function recordCommandOutcome(sessionKey: string, canonical: string, success: boolean): void;
|
|
49
|
+
export interface PushGateResult {
|
|
50
|
+
blocked: boolean;
|
|
51
|
+
/** Canonical commands that failed and gate the push. */
|
|
52
|
+
failed: string[];
|
|
53
|
+
}
|
|
54
|
+
export declare function checkPushGate(sessionKey: string): PushGateResult;
|
|
55
|
+
/** Message returned in place of running a blocked `git push`. */
|
|
56
|
+
export declare function pushBlockedMessage(failed: string[]): string;
|
|
57
|
+
/** Sensitive paths present in `cwd` that a broad `git add` would likely sweep in. */
|
|
58
|
+
export declare function detectSensitiveStaging(cwd: string): string[];
|
|
59
|
+
/** Non-blocking warning appended to a broad-stage command's output, or "". */
|
|
60
|
+
export declare function stagingWarning(cwd: string): string;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tools/git-safety.ts
|
|
3
|
+
*
|
|
4
|
+
* Pre-execution git safety for the bash tool. Distilled from a real session
|
|
5
|
+
* audit (18285908637a) where a cheap model:
|
|
6
|
+
* 1. pushed while 24 tests were failing (it batched `git add -A && commit &&
|
|
7
|
+
* push` in the SAME tool batch as the test run, so push was never gated
|
|
8
|
+
* on the result), and
|
|
9
|
+
* 2. swept the CLI's own `.muonroi-cli/settings.json` (which can hold
|
|
10
|
+
* provider API keys) into a public repo via `git add -A`.
|
|
11
|
+
*
|
|
12
|
+
* Two guards, both cheap and side-effect-free:
|
|
13
|
+
* - PUSH GATE: a session-scoped record of failed verification commands
|
|
14
|
+
* (test/build/lint/typecheck). A `git push` is BLOCKED (not executed)
|
|
15
|
+
* while any verification command has failed this session and not been
|
|
16
|
+
* re-run green. Mirrors the repo's mandatory Pre-Push Test Gate.
|
|
17
|
+
* - STAGING WARNING: a non-blocking warning when a broad `git add -A` /
|
|
18
|
+
* `git add .` / `git commit -a` is run while sensitive paths (`.env`,
|
|
19
|
+
* `.muonroi-cli/`, private keys, credentials) exist in the repo root.
|
|
20
|
+
*
|
|
21
|
+
* The push gate is deterministic for the sequential case (a failed check then
|
|
22
|
+
* a later push). The concurrent case (push batched in the same parallel tool
|
|
23
|
+
* call as the check) is covered by the system-prompt git-safety clause.
|
|
24
|
+
*
|
|
25
|
+
* Override the push gate with `MUONROI_ALLOW_PUSH_ON_RED=1`.
|
|
26
|
+
*/
|
|
27
|
+
import * as fs from "node:fs";
|
|
28
|
+
import * as path from "node:path";
|
|
29
|
+
import { isVerificationCommand } from "../orchestrator/tool-args-hash.js";
|
|
30
|
+
function getState() {
|
|
31
|
+
if (!globalThis.__muonroiGitSafetyState) {
|
|
32
|
+
globalThis.__muonroiGitSafetyState = new Map();
|
|
33
|
+
}
|
|
34
|
+
return globalThis.__muonroiGitSafetyState;
|
|
35
|
+
}
|
|
36
|
+
/** Test helper — clear all session git-safety state. */
|
|
37
|
+
export function __resetGitSafetyState() {
|
|
38
|
+
globalThis.__muonroiGitSafetyState = new Map();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Blank out single/double-quoted substrings so a commit MESSAGE that merely
|
|
42
|
+
* mentions "git push" or "-a" never trips the classifiers. Cheap and good
|
|
43
|
+
* enough — we only need to avoid matching inside obvious quoted args.
|
|
44
|
+
*/
|
|
45
|
+
function stripQuoted(command) {
|
|
46
|
+
return command.replace(/"[^"]*"/g, '""').replace(/'[^']*'/g, "''");
|
|
47
|
+
}
|
|
48
|
+
// Each classifier scopes to ONE shell clause: `[^|&;\n]*` stops at pipes,
|
|
49
|
+
// `&`/`;`, AND newlines (all bash clause separators), so a separate command on
|
|
50
|
+
// another line (`git status\necho push`) cannot bleed into a false match. The
|
|
51
|
+
// `[ \t]` before the subcommand (not `\s`) keeps the match on one line and
|
|
52
|
+
// avoids `--grep=push` / `=push`. Quote-stripping upstream avoids commit-message
|
|
53
|
+
// hits. Allowing `[^|&;\n]*` between `git` and the subcommand covers global
|
|
54
|
+
// options like `git -c key=val push`.
|
|
55
|
+
const PUSH_RE = /\bgit\b[^|&;\n]*[ \t]push\b/;
|
|
56
|
+
const BROAD_ADD_RE = /\bgit\b[^|&;\n]*[ \t]add[ \t]+(?:-A\b|--all\b|\.(?=[ \t]|$))/;
|
|
57
|
+
// `-a` / `-am` / `-ma` etc. — a short-flag cluster CONTAINING `a`, terminated by
|
|
58
|
+
// whitespace or end (so `-a--otherflag` and `--amend` do NOT match).
|
|
59
|
+
const COMMIT_ALL_RE = /\bgit\b[^|&;\n]*[ \t]commit\b[^|&;\n]*[ \t]-[a-z]*a[a-z]*(?=[ \t]|$)/;
|
|
60
|
+
export function analyzeGitCommand(command) {
|
|
61
|
+
const c = stripQuoted(command);
|
|
62
|
+
return {
|
|
63
|
+
isPush: PUSH_RE.test(c),
|
|
64
|
+
isBroadStage: BROAD_ADD_RE.test(c) || COMMIT_ALL_RE.test(c),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Record the outcome of a bash command for the push gate. Only verification
|
|
69
|
+
* commands (test/build/lint/typecheck) are tracked. A pass clears that exact
|
|
70
|
+
* command's failed flag; a failure sets it. Non-verification commands are
|
|
71
|
+
* ignored.
|
|
72
|
+
*/
|
|
73
|
+
export function recordCommandOutcome(sessionKey, canonical, success) {
|
|
74
|
+
if (!canonical || !isVerificationCommand(canonical))
|
|
75
|
+
return;
|
|
76
|
+
const state = getState();
|
|
77
|
+
let entry = state.get(sessionKey);
|
|
78
|
+
if (!entry) {
|
|
79
|
+
entry = { failedVerifies: new Map() };
|
|
80
|
+
state.set(sessionKey, entry);
|
|
81
|
+
}
|
|
82
|
+
if (success) {
|
|
83
|
+
entry.failedVerifies.delete(canonical);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
entry.failedVerifies.set(canonical, Date.now());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function checkPushGate(sessionKey) {
|
|
90
|
+
if (process.env.MUONROI_ALLOW_PUSH_ON_RED === "1")
|
|
91
|
+
return { blocked: false, failed: [] };
|
|
92
|
+
const entry = getState().get(sessionKey);
|
|
93
|
+
if (!entry || entry.failedVerifies.size === 0)
|
|
94
|
+
return { blocked: false, failed: [] };
|
|
95
|
+
return { blocked: true, failed: [...entry.failedVerifies.keys()] };
|
|
96
|
+
}
|
|
97
|
+
/** Message returned in place of running a blocked `git push`. */
|
|
98
|
+
export function pushBlockedMessage(failed) {
|
|
99
|
+
const list = failed.map((c) => ` • ${c}`).join("\n");
|
|
100
|
+
return ("BLOCKED: refusing to run `git push` — a verification command failed earlier this session and has not passed since:\n" +
|
|
101
|
+
`${list}\n\n` +
|
|
102
|
+
"Re-run the failing check until it passes (0 failures), then push. This mirrors the mandatory Pre-Push Test Gate " +
|
|
103
|
+
"(never push on red). If you must override for a genuine reason, set MUONROI_ALLOW_PUSH_ON_RED=1.");
|
|
104
|
+
}
|
|
105
|
+
// Repo-root paths that should essentially never be committed. Presence-based
|
|
106
|
+
// (fs.existsSync) so the check is O(1) and never spawns git.
|
|
107
|
+
const SENSITIVE_NAMES = [
|
|
108
|
+
".env",
|
|
109
|
+
".env.local",
|
|
110
|
+
".env.production",
|
|
111
|
+
".env.development",
|
|
112
|
+
".muonroi-cli",
|
|
113
|
+
"id_rsa",
|
|
114
|
+
"id_ed25519",
|
|
115
|
+
"credentials.json",
|
|
116
|
+
".npmrc",
|
|
117
|
+
];
|
|
118
|
+
/** Sensitive paths present in `cwd` that a broad `git add` would likely sweep in. */
|
|
119
|
+
export function detectSensitiveStaging(cwd) {
|
|
120
|
+
const found = [];
|
|
121
|
+
for (const name of SENSITIVE_NAMES) {
|
|
122
|
+
try {
|
|
123
|
+
if (fs.existsSync(path.join(cwd, name)))
|
|
124
|
+
found.push(name);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// ignore unreadable entries — best-effort detection only
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return found;
|
|
131
|
+
}
|
|
132
|
+
/** Non-blocking warning appended to a broad-stage command's output, or "". */
|
|
133
|
+
export function stagingWarning(cwd) {
|
|
134
|
+
const sensitive = detectSensitiveStaging(cwd);
|
|
135
|
+
if (sensitive.length === 0)
|
|
136
|
+
return "";
|
|
137
|
+
return ("\n\n[WARNING: a broad `git add`/`git commit -a` was run while these sensitive paths exist in the repo root: " +
|
|
138
|
+
`${sensitive.join(", ")}. Verify none were staged (git status), stage files EXPLICITLY (git add <path>), and ensure ` +
|
|
139
|
+
"secrets are gitignored before committing/pushing.]");
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=git-safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|