amicus 4.6.3 → 4.7.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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +283 -0
- package/README.md +19 -7
- package/bin/amicus.js +31 -0
- package/docs/ROADMAP.md +143 -36
- package/docs/configuration.md +51 -3
- package/docs/council.md +63 -10
- package/docs/doc-system.md +8 -7
- package/docs/schemas.md +10 -1
- package/docs/troubleshooting.md +27 -1
- package/docs/usage.md +59 -13
- package/electron/workspace-ui/index.html +3 -0
- package/electron/workspace-ui/live-model.js +52 -14
- package/electron/workspace-ui/workspace-app.js +14 -3
- package/electron/workspace-ui/workspace-lazy.js +233 -0
- package/electron/workspace-ui/workspace-matrix.js +12 -1
- package/electron/workspace-ui/workspace-panels.js +24 -171
- package/electron/workspace-ui/workspace-render.js +6 -2
- package/electron/workspace-ui/workspace-seats.js +68 -0
- package/electron/workspace-ui/workspace.css +6 -0
- package/package.json +5 -2
- package/schemas/council-run.schema.json +1 -0
- package/schemas/council-stats.schema.json +9 -1
- package/schemas/run.schema.json +2 -1
- package/schemas/spend.schema.json +1 -1
- package/schemas/wave.schema.json +2 -1
- package/skills/second-opinion/MANUAL-ORCHESTRATION.md +12 -0
- package/skills/second-opinion/MODEL-NOTES.md +5 -4
- package/skills/sidecar/SKILL.md +7 -2
- package/src/cli-council-run-bench.js +86 -0
- package/src/cli-handlers-council-run.js +65 -81
- package/src/cli-handlers-council.js +17 -5
- package/src/cli-handlers-fanout.js +179 -0
- package/src/cli-handlers-pack.js +24 -10
- package/src/cli-handlers-run.js +19 -161
- package/src/cli-template-args.js +48 -0
- package/src/cli.js +39 -46
- package/src/council/debate.js +89 -10
- package/src/council/ledger.js +72 -11
- package/src/council/report.js +17 -6
- package/src/council/run-assemble.js +15 -3
- package/src/council/run-budget.js +2 -2
- package/src/council/run-chair.js +61 -5
- package/src/council/run-debate.js +51 -67
- package/src/council/run-launch.js +9 -2
- package/src/council/run-retry.js +4 -1
- package/src/council/run-stage1-launch.js +94 -0
- package/src/council/run-stage2.js +25 -4
- package/src/council/run-stages.js +79 -86
- package/src/council/run-state.js +10 -2
- package/src/council/run.js +26 -2
- package/src/council/tally.js +6 -2
- package/src/mcp-council-awareness.js +1 -0
- package/src/mcp-council-bench.js +4 -0
- package/src/mcp-council-run.js +10 -0
- package/src/mcp-server.js +114 -54
- package/src/mcp-tools.js +12 -5
- package/src/pack/pack-cli.js +1 -1
- package/src/pack/pack-forward.js +12 -4
- package/src/pack/pack-resolve.js +3 -0
- package/src/pack/pack-store.js +20 -3
- package/src/pack/pack-validate.js +5 -1
- package/src/sidecar/budget.js +38 -4
- package/src/sidecar/fanout-budget.js +1 -2
- package/src/sidecar/fanout-leg-fallback.js +7 -3
- package/src/sidecar/fanout-wave-io.js +13 -1
- package/src/sidecar/fanout.js +11 -9
- package/src/sidecar/list-limit.js +50 -0
- package/src/sidecar/list-search.js +69 -0
- package/src/sidecar/read.js +90 -5
- package/src/sidecar/start-metadata.js +58 -0
- package/src/sidecar/start.js +8 -43
- package/src/sidecar/workspace-auto-open.js +2 -2
- package/src/spend-query.js +2 -1
- package/src/template/apply.js +7 -4
- package/src/template/render.js +6 -2
- package/src/template/store.js +1 -1
- package/src/utils/cli-preflight.js +27 -1
- package/src/utils/config.js +15 -0
- package/src/utils/result-schema-rebuild.js +1 -0
- package/src/utils/result-schema.js +6 -1
- package/src/utils/session-index-tmp-sweep.js +18 -3
- package/src/utils/session-index.js +1 -0
- package/src/utils/session-metadata-tmp-sweep.js +24 -4
- package/src/utils/spend-ledger.js +11 -4
- package/src/utils/validators.js +16 -0
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
* symlinked taskId or subagents directory could otherwise be traversed and
|
|
30
30
|
* have files unlinked through the link, effectively outside the sessions
|
|
31
31
|
* root; lstat closes that off at zero cost.
|
|
32
|
+
*
|
|
33
|
+
* Consequence of the SR-3 isFile() gate (listTmpIn, below): a SYMLINK whose
|
|
34
|
+
* basename matches the tmp pattern is now excluded from the list entirely —
|
|
35
|
+
* neither swept nor reported. Before SR-3 it was swept (unlink removes the
|
|
36
|
+
* link, never the target — a safe success). Deliberate: this module's
|
|
37
|
+
* never-follow policy applies to the entries it unlinks too. Note the sibling
|
|
38
|
+
* session-index-tmp-sweep.js diverges here — it uses statSync, so a
|
|
39
|
+
* symlink-to-a-file with the matching name IS still swept there.
|
|
32
40
|
*/
|
|
33
41
|
|
|
34
42
|
const fs = require('fs');
|
|
@@ -38,7 +46,14 @@ const HINTS = require('./remediation-hints');
|
|
|
38
46
|
/** Files older than this survive to the next --fix, never a live writer's ms-lived tmp. */
|
|
39
47
|
const AGE_THRESHOLD_MS = 60 * 1000;
|
|
40
48
|
|
|
41
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* The cwd-scoped sessions root: <cwd>/.claude/amicus_sessions.
|
|
51
|
+
* Reads process.cwd() directly, NOT doctor's injected getCwd
|
|
52
|
+
* (cli-handlers-doctor.js's realDeps().getCwd) — the
|
|
53
|
+
* listSessionMetadataTmpFiles/unlinkSessionMetadataTmp deps are wired
|
|
54
|
+
* argument-free in that same realDeps(), so that seam does not reach here.
|
|
55
|
+
* Thread cwd through those deps if a `doctor --cwd <dir>` mode ever lands.
|
|
56
|
+
*/
|
|
42
57
|
function sessionsRoot() {
|
|
43
58
|
const { SESSIONS_DIR } = require('../session-manager');
|
|
44
59
|
return path.join(process.cwd(), '.claude', SESSIONS_DIR);
|
|
@@ -56,9 +71,9 @@ function listTmpIn(dir, root) {
|
|
|
56
71
|
return entries
|
|
57
72
|
.filter(isMetadataTmp)
|
|
58
73
|
.map((basename) => {
|
|
59
|
-
let
|
|
60
|
-
try {
|
|
61
|
-
return { name: path.relative(root, path.join(dir, basename)), mtimeMs };
|
|
74
|
+
let st = null;
|
|
75
|
+
try { st = fs.lstatSync(path.join(dir, basename)); } catch { /* raced away */ }
|
|
76
|
+
return { name: path.relative(root, path.join(dir, basename)), mtimeMs: st && st.isFile() ? st.mtimeMs : null };
|
|
62
77
|
})
|
|
63
78
|
.filter((f) => f.mtimeMs !== null);
|
|
64
79
|
}
|
|
@@ -107,6 +122,11 @@ function unlinkSessionMetadataTmp(name) {
|
|
|
107
122
|
* wraps this in guard() the same way it wires the sibling sessions-index-tmp check.
|
|
108
123
|
* @param {{listSessionMetadataTmpFiles: () => Array<{name:string, mtimeMs:number}>,
|
|
109
124
|
* fix?: boolean, now: () => number, unlinkSessionMetadataTmp: (name: string) => void}} d
|
|
125
|
+
* The four `message` strings below are byte-identical to the index sibling's
|
|
126
|
+
* (session-index-tmp-sweep.js's evaluateSessionIndexTmpSweep, same four
|
|
127
|
+
* ok/warn returns) on purpose — `id`/`name` and the `fixDetail` wording are
|
|
128
|
+
* the only disambiguators between the two rows. Reword one side and the
|
|
129
|
+
* pairing silently breaks: reword both, or neither.
|
|
110
130
|
*/
|
|
111
131
|
function evaluateSessionMetadataTmpSweep(d) {
|
|
112
132
|
const id = 'session-metadata-tmp'; const name = 'Session metadata tmp files';
|
|
@@ -55,13 +55,17 @@ const SPEND_LEDGER_FILE = 'spend-ledger.jsonl';
|
|
|
55
55
|
* @param {string} [opts.councilName] council name (additive attribution)
|
|
56
56
|
* @param {string} [opts.project] project directory (additive attribution)
|
|
57
57
|
* @param {string} [opts.gateway] resolved gateway ('direct'|'openrouter'|'local', additive attribution)
|
|
58
|
+
* @param {string} [opts.tag] user-chosen tag (v4.7 F8 D16, additive attribution) —
|
|
59
|
+
* nullable dim, same null-not-absent convention as councilRunId/project below;
|
|
60
|
+
* deliberately the OPPOSITE convention from createSessionMetadata's tag (D13,
|
|
61
|
+
* absent-not-null) — see the dim-list comment below.
|
|
58
62
|
* @param {number} [opts.attempt] fallback attempt count (omitted if absent)
|
|
59
63
|
* @param {string} [opts.substitutedFor] substituted model (omitted if absent)
|
|
60
64
|
* @param {string} [opts.retryOfWaveId] wave id being retried (omitted if absent)
|
|
61
65
|
* @param {{dir?:string}} [ctx] test seam — dir overrides getConfigDir()
|
|
62
66
|
*/
|
|
63
67
|
function appendSpend({ taskId, waveId, model, mode, usage,
|
|
64
|
-
op, status, councilRunId, councilName, project, gateway,
|
|
68
|
+
op, status, councilRunId, councilName, project, gateway, tag,
|
|
65
69
|
attempt, substitutedFor, retryOfWaveId }, ctx = {}) {
|
|
66
70
|
if (!usage) { return; }
|
|
67
71
|
try {
|
|
@@ -76,15 +80,18 @@ function appendSpend({ taskId, waveId, model, mode, usage,
|
|
|
76
80
|
mode: mode || null,
|
|
77
81
|
tokens: usage.tokens || null,
|
|
78
82
|
cost: usage.cost || null,
|
|
79
|
-
// v4.3 additive attribution (spec 7.1).
|
|
80
|
-
//
|
|
81
|
-
//
|
|
83
|
+
// v4.3 additive attribution (spec 7.1), extended v4.7 F8 D16 with `tag`.
|
|
84
|
+
// Nullable dimensions (op/status/councilRunId/councilName/project/
|
|
85
|
+
// gateway/tag) default to null (so a row is always groupable); linkage
|
|
86
|
+
// fields are OMITTED unless present (they only exist on fallback/retry
|
|
87
|
+
// rows).
|
|
82
88
|
op: op || null,
|
|
83
89
|
status: status || null,
|
|
84
90
|
councilRunId: councilRunId || null,
|
|
85
91
|
councilName: councilName || null,
|
|
86
92
|
project: project || null,
|
|
87
93
|
gateway: gateway || null,
|
|
94
|
+
tag: tag || null,
|
|
88
95
|
};
|
|
89
96
|
if (attempt !== undefined) { row.attempt = attempt; }
|
|
90
97
|
if (substitutedFor !== undefined) { row.substitutedFor = substitutedFor; }
|
package/src/utils/validators.js
CHANGED
|
@@ -24,6 +24,20 @@ const { PROVIDER_KEY_MAP } = require('./provider-registry');
|
|
|
24
24
|
/** Task ID format: alphanumeric, hyphens, underscores, 1-64 chars */
|
|
25
25
|
const TASK_ID_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
26
26
|
|
|
27
|
+
const TAG_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
28
|
+
/**
|
|
29
|
+
* v4.7 F8 (D13): tag validation REJECTS (unlike sanitizeCouncilName, which
|
|
30
|
+
* cleans) — a stored tag is a user-chosen search key, so silent truncation
|
|
31
|
+
* or charset-stripping would make `--search`/`--group-by tag` miss it.
|
|
32
|
+
* Non-string guards the valueless `--tag` parse (cli.js turns it into true).
|
|
33
|
+
*/
|
|
34
|
+
function validateTag(value) {
|
|
35
|
+
if (typeof value !== 'string' || !TAG_PATTERN.test(value)) {
|
|
36
|
+
return { ok: false, error: 'Invalid --tag: 1-64 chars, letters/digits/_/- only' };
|
|
37
|
+
}
|
|
38
|
+
return { ok: true, tag: value };
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
/**
|
|
28
42
|
* Validate a task ID format (safe for use in file paths)
|
|
29
43
|
* @param {string} taskId
|
|
@@ -249,6 +263,8 @@ module.exports = {
|
|
|
249
263
|
MODEL_THINKING_SUPPORT,
|
|
250
264
|
TASK_ID_PATTERN,
|
|
251
265
|
validateTaskId,
|
|
266
|
+
TAG_PATTERN,
|
|
267
|
+
validateTag,
|
|
252
268
|
safeSessionDir,
|
|
253
269
|
validatePromptContent,
|
|
254
270
|
validateCwdPath,
|