brainclaw 0.28.0 → 1.5.3
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/README.md +193 -170
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +683 -23
- package/dist/commands/accept.js +3 -0
- package/dist/commands/add-step.js +11 -26
- package/dist/commands/agent-board.js +70 -3
- package/dist/commands/audit.js +19 -0
- package/dist/commands/check-policy.js +54 -0
- package/dist/commands/check-security-mcp.js +145 -0
- package/dist/commands/check-security.js +106 -0
- package/dist/commands/claim-resource.js +1 -0
- package/dist/commands/codev.js +672 -0
- package/dist/commands/compact.js +74 -0
- package/dist/commands/complete-step.js +16 -26
- package/dist/commands/constraint.js +8 -20
- package/dist/commands/decision.js +9 -20
- package/dist/commands/delete-plan.js +10 -12
- package/dist/commands/delete-step.js +16 -0
- package/dist/commands/dispatch.js +163 -0
- package/dist/commands/doctor.js +1122 -49
- package/dist/commands/enable-agent.js +1 -0
- package/dist/commands/export.js +280 -22
- package/dist/commands/handoff.js +33 -0
- package/dist/commands/harvest.js +189 -0
- package/dist/commands/hooks.js +82 -25
- package/dist/commands/inbox.js +169 -0
- package/dist/commands/init.js +38 -31
- package/dist/commands/install-hooks.js +71 -44
- package/dist/commands/link.js +89 -0
- package/dist/commands/list-claims.js +48 -3
- package/dist/commands/list-plans.js +129 -25
- package/dist/commands/loops-handlers.js +409 -0
- package/dist/commands/mcp-read-handlers.js +1628 -0
- package/dist/commands/mcp-schemas.generated.js +74 -0
- package/dist/commands/mcp.js +4244 -1475
- package/dist/commands/plan-resource.js +64 -0
- package/dist/commands/plan.js +12 -26
- package/dist/commands/prune.js +37 -2
- package/dist/commands/reflect.js +20 -7
- package/dist/commands/release-claim.js +11 -6
- package/dist/commands/release-notes.js +170 -0
- package/dist/commands/repair.js +210 -0
- package/dist/commands/run-profile.js +57 -0
- package/dist/commands/sequence.js +113 -0
- package/dist/commands/session-end.js +423 -14
- package/dist/commands/session-start.js +214 -41
- package/dist/commands/setup-security.js +103 -0
- package/dist/commands/setup.js +42 -4
- package/dist/commands/stale.js +109 -0
- package/dist/commands/switch.js +131 -10
- package/dist/commands/trap.js +14 -31
- package/dist/commands/update-handoff.js +63 -4
- package/dist/commands/update-plan.js +21 -28
- package/dist/commands/update-step.js +37 -0
- package/dist/commands/upgrade.js +313 -6
- package/dist/commands/usage.js +102 -0
- package/dist/commands/version.js +20 -0
- package/dist/commands/who.js +124 -0
- package/dist/commands/worktree.js +105 -0
- package/dist/core/actions.js +315 -0
- package/dist/core/agent-capability.js +610 -17
- package/dist/core/agent-context.js +7 -1
- package/dist/core/agent-files.js +1169 -85
- package/dist/core/agent-integrations.js +160 -5
- package/dist/core/agent-inventory.js +2 -0
- package/dist/core/agent-profiles.js +93 -0
- package/dist/core/agent-registry.js +162 -30
- package/dist/core/agentrun-reconciler.js +345 -0
- package/dist/core/agentruns.js +424 -0
- package/dist/core/ai-agent-detection.js +31 -10
- package/dist/core/archival.js +77 -0
- package/dist/core/assignment-sweeper.js +82 -0
- package/dist/core/assignments.js +367 -0
- package/dist/core/audit.js +30 -0
- package/dist/core/bootstrap.js +61 -10
- package/dist/core/brainclaw-version.js +94 -2
- package/dist/core/candidates.js +93 -2
- package/dist/core/claims.js +419 -0
- package/dist/core/codev-metrics.js +77 -0
- package/dist/core/codev-personas.js +31 -0
- package/dist/core/codev-plan-gen.js +35 -0
- package/dist/core/codev-prompts.js +74 -0
- package/dist/core/codev-responses.js +62 -0
- package/dist/core/codev-rounds.js +218 -0
- package/dist/core/config.js +4 -0
- package/dist/core/context.js +454 -34
- package/dist/core/coordination.js +201 -6
- package/dist/core/cross-project.js +230 -16
- package/dist/core/default-profiles/doctor.yaml +11 -0
- package/dist/core/default-profiles/janitor.yaml +11 -0
- package/dist/core/default-profiles/onboarder.yaml +11 -0
- package/dist/core/default-profiles/reviewer.yaml +13 -0
- package/dist/core/dispatcher.js +1189 -0
- package/dist/core/duplicates.js +2 -2
- package/dist/core/entity-operations.js +450 -0
- package/dist/core/entity-registry.js +344 -0
- package/dist/core/event-log.js +1 -0
- package/dist/core/events.js +106 -2
- package/dist/core/execution-adapters.js +154 -0
- package/dist/core/execution-context.js +63 -0
- package/dist/core/execution-profile.js +270 -0
- package/dist/core/execution.js +255 -0
- package/dist/core/facade-schema.js +81 -0
- package/dist/core/federation-cloud.js +99 -0
- package/dist/core/federation-message.js +52 -0
- package/dist/core/federation-transport.js +65 -0
- package/dist/core/gc-semantic.js +482 -0
- package/dist/core/governance.js +247 -0
- package/dist/core/guards.js +19 -0
- package/dist/core/ideation.js +72 -0
- package/dist/core/identity.js +252 -28
- package/dist/core/ids.js +6 -0
- package/dist/core/input-validation.js +2 -2
- package/dist/core/instruction-templates.js +344 -136
- package/dist/core/io.js +90 -11
- package/dist/core/lock.js +6 -2
- package/dist/core/loops/brief-assembly.js +213 -0
- package/dist/core/loops/facade-schema.js +148 -0
- package/dist/core/loops/index.js +7 -0
- package/dist/core/loops/iteration-engine.js +139 -0
- package/dist/core/loops/lock.js +385 -0
- package/dist/core/loops/store.js +201 -0
- package/dist/core/loops/types.js +403 -0
- package/dist/core/loops/verbs.js +534 -0
- package/dist/core/markdown.js +15 -3
- package/dist/core/memory-compactor.js +432 -0
- package/dist/core/memory-git.js +152 -8
- package/dist/core/messaging.js +278 -0
- package/dist/core/migration.js +32 -1
- package/dist/core/mutation-pipeline.js +4 -2
- package/dist/core/operations/memory-mutation.js +129 -0
- package/dist/core/operations/memory-write.js +78 -0
- package/dist/core/operations/plan.js +190 -0
- package/dist/core/policy.js +169 -0
- package/dist/core/repo-analysis.js +67 -0
- package/dist/core/reputation.js +9 -3
- package/dist/core/schema.js +546 -21
- package/dist/core/search.js +21 -2
- package/dist/core/security-cache.js +71 -0
- package/dist/core/security-guard.js +152 -0
- package/dist/core/security-scoring.js +86 -0
- package/dist/core/sequence.js +130 -0
- package/dist/core/socket-client.js +113 -0
- package/dist/core/staleness.js +246 -0
- package/dist/core/state.js +98 -22
- package/dist/core/store-resolution.js +54 -12
- package/dist/core/toml-writer.js +76 -0
- package/dist/core/upgrades/backup.js +232 -0
- package/dist/core/upgrades/health-check.js +169 -0
- package/dist/core/upgrades/patches/candidate-archive.js +145 -0
- package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
- package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
- package/dist/core/upgrades/schema-version.js +97 -0
- package/dist/core/worktree.js +606 -0
- package/dist/facts.js +114 -0
- package/dist/facts.json +111 -0
- package/docs/architecture/project-refs.md +5 -1
- package/docs/cli.md +690 -43
- package/docs/concepts/ideation-loop.md +317 -0
- package/docs/concepts/loop-engine.md +456 -0
- package/docs/concepts/mcp-governance.md +268 -0
- package/docs/concepts/memory-staleness.md +122 -0
- package/docs/concepts/multi-agent-workflows.md +166 -0
- package/docs/concepts/plans-and-claims.md +31 -6
- package/docs/concepts/project-md-convention.md +35 -0
- package/docs/concepts/troubleshooting.md +220 -0
- package/docs/concepts/upgrade-cli.md +202 -0
- package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
- package/docs/context-format-changelog.md +2 -2
- package/docs/context-format.md +2 -2
- package/docs/index.md +68 -0
- package/docs/integrations/agents.md +15 -16
- package/docs/integrations/cline.md +88 -0
- package/docs/integrations/codex.md +75 -23
- package/docs/integrations/continue.md +60 -0
- package/docs/integrations/copilot.md +67 -9
- package/docs/integrations/kilocode.md +72 -0
- package/docs/integrations/mcp.md +304 -21
- package/docs/integrations/mistral-vibe.md +122 -0
- package/docs/integrations/opencode.md +84 -0
- package/docs/integrations/overview.md +23 -8
- package/docs/integrations/roo.md +74 -0
- package/docs/integrations/windsurf.md +83 -0
- package/docs/mcp-schema-changelog.md +191 -1
- package/docs/playbooks/integration/index.md +121 -0
- package/docs/playbooks/productivity/index.md +102 -0
- package/docs/playbooks/team/index.md +122 -0
- package/docs/product/agent-first-model.md +184 -0
- package/docs/product/entity-model-audit.md +462 -0
- package/docs/quickstart-existing-project.md +135 -0
- package/docs/quickstart.md +124 -37
- package/docs/release-maintenance.md +79 -0
- package/docs/review.md +2 -0
- package/docs/server-operations.md +118 -0
- package/package.json +20 -12
- package/dist/commands/claude-desktop-extension.js +0 -18
- package/dist/commands/diff.js +0 -99
- package/dist/core/claude-desktop-extension.js +0 -224
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { BrainclawLocalReleaseManifestSchema, } from './schema.js';
|
|
6
|
+
import { AgentReleaseNotesSchema, BrainclawLocalReleaseManifestSchema, } from './schema.js';
|
|
7
7
|
export const DEFAULT_LOCAL_RELEASES_DIR = '.releases';
|
|
8
8
|
export const DEFAULT_LOCAL_RELEASE_MANIFEST_PATH = `${DEFAULT_LOCAL_RELEASES_DIR}/brainclaw-local.json`;
|
|
9
9
|
export const DEFAULT_NPM_UPDATE_PACKAGE = 'brainclaw';
|
|
@@ -15,7 +15,75 @@ const DEFAULT_NPM_UPDATE_SOURCE = {
|
|
|
15
15
|
package_name: DEFAULT_NPM_UPDATE_PACKAGE,
|
|
16
16
|
dist_tag: DEFAULT_NPM_UPDATE_DIST_TAG,
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Scan PATH for all brainclaw installations and their versions.
|
|
20
|
+
* Detects when multiple versions are installed (e.g. global + user-local)
|
|
21
|
+
* which causes confusion about which version is actually running.
|
|
22
|
+
*/
|
|
23
|
+
export function detectConcurrentInstallations() {
|
|
24
|
+
const currentVersion = getInstalledBrainclawVersion();
|
|
25
|
+
const installations = [];
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
// On Windows, `where` returns all matches. On Unix, `which -a` does.
|
|
28
|
+
const isWindows = os.platform() === 'win32';
|
|
29
|
+
const result = isWindows
|
|
30
|
+
? spawnSync('where', ['brainclaw'], { encoding: 'utf-8', timeout: 5000 })
|
|
31
|
+
: spawnSync('which', ['-a', 'brainclaw'], { encoding: 'utf-8', timeout: 5000 });
|
|
32
|
+
if (result.status !== 0)
|
|
33
|
+
return [];
|
|
34
|
+
const paths = result.stdout.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
35
|
+
for (const binPath of paths) {
|
|
36
|
+
// Normalize to avoid duplicates from symlinks
|
|
37
|
+
let realPath;
|
|
38
|
+
try {
|
|
39
|
+
realPath = isWindows ? binPath : fs.realpathSync(binPath);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
realPath = binPath;
|
|
43
|
+
}
|
|
44
|
+
if (seen.has(realPath))
|
|
45
|
+
continue;
|
|
46
|
+
seen.add(realPath);
|
|
47
|
+
// Get version by running the binary
|
|
48
|
+
let version = 'unknown';
|
|
49
|
+
try {
|
|
50
|
+
const vResult = spawnSync(binPath, ['--version'], { encoding: 'utf-8', timeout: 5000 });
|
|
51
|
+
if (vResult.status === 0) {
|
|
52
|
+
version = vResult.stdout.trim().split(/\r?\n/)[0]?.trim() ?? 'unknown';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch { /* ignore */ }
|
|
56
|
+
installations.push({
|
|
57
|
+
path: binPath,
|
|
58
|
+
version,
|
|
59
|
+
isCurrent: version === currentVersion,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return installations;
|
|
63
|
+
}
|
|
18
64
|
let cachedCliVersion;
|
|
65
|
+
let cachedPackageJsonPath;
|
|
66
|
+
/**
|
|
67
|
+
* Read the brainclaw version from disk (package.json), bypassing the in-memory cache.
|
|
68
|
+
* Used by the MCP server to detect when a new version has been installed while the
|
|
69
|
+
* long-running MCP process is still running with old code.
|
|
70
|
+
*/
|
|
71
|
+
export function readDiskBrainclawVersion() {
|
|
72
|
+
if (cachedPackageJsonPath === undefined) {
|
|
73
|
+
cachedPackageJsonPath = findOwnPackageJson() ?? '';
|
|
74
|
+
}
|
|
75
|
+
if (!cachedPackageJsonPath)
|
|
76
|
+
return '0.0.0';
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(fs.readFileSync(cachedPackageJsonPath, 'utf-8'));
|
|
79
|
+
return typeof parsed.version === 'string' && parsed.version.trim().length > 0
|
|
80
|
+
? parsed.version.trim()
|
|
81
|
+
: '0.0.0';
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return '0.0.0';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
19
87
|
export function getInstalledBrainclawVersion() {
|
|
20
88
|
if (cachedCliVersion) {
|
|
21
89
|
return cachedCliVersion;
|
|
@@ -126,7 +194,23 @@ export function renderBrainclawInstallableUpdateNotice(updateCheck) {
|
|
|
126
194
|
if (updateCheck.install_command) {
|
|
127
195
|
lines.push(`Install: ${updateCheck.install_command}`);
|
|
128
196
|
}
|
|
129
|
-
|
|
197
|
+
const arn = updateCheck.agent_release_notes;
|
|
198
|
+
if (arn) {
|
|
199
|
+
lines.push(`Summary: ${arn.summary}`);
|
|
200
|
+
if (arn.breaking_risk && arn.breaking_risk !== 'none') {
|
|
201
|
+
lines.push(`Breaking risk: ${arn.breaking_risk}`);
|
|
202
|
+
}
|
|
203
|
+
if (arn.highlights && arn.highlights.length > 0) {
|
|
204
|
+
lines.push('Highlights:');
|
|
205
|
+
for (const h of arn.highlights) {
|
|
206
|
+
lines.push(` • ${h}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (arn.action_recommendation) {
|
|
210
|
+
lines.push(`Action: ${arn.action_recommendation}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (updateCheck.release_notes) {
|
|
130
214
|
lines.push(`Why update: ${updateCheck.release_notes}`);
|
|
131
215
|
}
|
|
132
216
|
return lines.join('\n');
|
|
@@ -161,6 +245,9 @@ export function publishLocalBrainclawRelease(cwd, options = {}) {
|
|
|
161
245
|
const projectManifestPath = toPortablePath(path.relative(cwd, manifestPath));
|
|
162
246
|
const installCommand = `npm install -g "${projectArtifactPath}"`;
|
|
163
247
|
const releaseNotes = options.releaseNotes?.trim() || null;
|
|
248
|
+
const agentReleaseNotes = options.agentReleaseNotes
|
|
249
|
+
? AgentReleaseNotesSchema.parse(options.agentReleaseNotes)
|
|
250
|
+
: null;
|
|
164
251
|
const manifest = BrainclawLocalReleaseManifestSchema.parse({
|
|
165
252
|
version: 1,
|
|
166
253
|
channel: 'local-pack',
|
|
@@ -170,6 +257,7 @@ export function publishLocalBrainclawRelease(cwd, options = {}) {
|
|
|
170
257
|
artifact_path: manifestArtifactPath,
|
|
171
258
|
install_command: installCommand,
|
|
172
259
|
release_notes: releaseNotes ?? undefined,
|
|
260
|
+
agent_release_notes: agentReleaseNotes ?? undefined,
|
|
173
261
|
});
|
|
174
262
|
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
|
|
175
263
|
return {
|
|
@@ -179,6 +267,7 @@ export function publishLocalBrainclawRelease(cwd, options = {}) {
|
|
|
179
267
|
artifact_path: projectArtifactPath,
|
|
180
268
|
install_command: installCommand,
|
|
181
269
|
release_notes: releaseNotes,
|
|
270
|
+
agent_release_notes: agentReleaseNotes,
|
|
182
271
|
};
|
|
183
272
|
}
|
|
184
273
|
function checkNpmInstallableUpdate(source, config, cwd, options, defaultSource) {
|
|
@@ -343,6 +432,7 @@ function checkLocalPackInstallableUpdate(manifestPath, config, cwd) {
|
|
|
343
432
|
const installCommand = manifest.install_command?.trim()
|
|
344
433
|
|| (artifactPath ? `npm install -g "${artifactPath}"` : config?.brainclaw_upgrade_command?.trim() || null);
|
|
345
434
|
const releaseNotes = manifest.release_notes?.trim() || config?.brainclaw_upgrade_message?.trim() || null;
|
|
435
|
+
const agentReleaseNotes = manifest.agent_release_notes ?? null;
|
|
346
436
|
const installedVersion = getInstalledBrainclawVersion();
|
|
347
437
|
if (compareVersions(installedVersion, latestVersion) < 0) {
|
|
348
438
|
return {
|
|
@@ -353,6 +443,7 @@ function checkLocalPackInstallableUpdate(manifestPath, config, cwd) {
|
|
|
353
443
|
artifact_path: artifactPath,
|
|
354
444
|
install_command: installCommand,
|
|
355
445
|
release_notes: releaseNotes,
|
|
446
|
+
agent_release_notes: agentReleaseNotes,
|
|
356
447
|
status: 'update_available',
|
|
357
448
|
message: `A newer installable brainclaw build is available: ${latestVersion} (installed ${installedVersion}).`,
|
|
358
449
|
default_source: false,
|
|
@@ -366,6 +457,7 @@ function checkLocalPackInstallableUpdate(manifestPath, config, cwd) {
|
|
|
366
457
|
artifact_path: artifactPath,
|
|
367
458
|
install_command: installCommand,
|
|
368
459
|
release_notes: releaseNotes,
|
|
460
|
+
agent_release_notes: agentReleaseNotes,
|
|
369
461
|
status: 'up_to_date',
|
|
370
462
|
message: `Installed brainclaw ${installedVersion} is up to date for the configured local-pack channel.`,
|
|
371
463
|
default_source: false,
|
package/dist/core/candidates.js
CHANGED
|
@@ -6,6 +6,39 @@ import { resolveEntityDir } from './io.js';
|
|
|
6
6
|
import { mutate } from './mutation-pipeline.js';
|
|
7
7
|
import { nowISO, getNextShortLabel } from './ids.js';
|
|
8
8
|
import { JsonStore } from './json-store.js';
|
|
9
|
+
import { refreshLiveCompanions } from '../commands/export.js';
|
|
10
|
+
/**
|
|
11
|
+
* Return the effective source for a candidate.
|
|
12
|
+
*
|
|
13
|
+
* Resolution order:
|
|
14
|
+
* 1. Explicit `source` field if set to a valid enum value ('auto'|'agent'|'human').
|
|
15
|
+
* 2. Inferred from `origin` free-text pattern (e.g. 'runtime-note:...' → 'agent',
|
|
16
|
+
* 'session-end:...' → 'auto', 'mcp:...' / 'cross-project:...' → 'agent').
|
|
17
|
+
* 3. Default 'human' for legacy items without any provenance.
|
|
18
|
+
*
|
|
19
|
+
* This default is only applied in memory — no files are rewritten.
|
|
20
|
+
*/
|
|
21
|
+
export function resolvedSource(candidate) {
|
|
22
|
+
if (candidate.source)
|
|
23
|
+
return candidate.source;
|
|
24
|
+
return inferSourceFromOrigin(candidate.origin);
|
|
25
|
+
}
|
|
26
|
+
/** Infer the enum `source` from a free-text `origin` pattern. */
|
|
27
|
+
export function inferSourceFromOrigin(origin) {
|
|
28
|
+
if (!origin)
|
|
29
|
+
return 'human';
|
|
30
|
+
if (origin.startsWith('session-end:'))
|
|
31
|
+
return 'auto';
|
|
32
|
+
if (origin.startsWith('runtime-note:'))
|
|
33
|
+
return 'agent';
|
|
34
|
+
if (origin.startsWith('mcp:'))
|
|
35
|
+
return 'agent';
|
|
36
|
+
if (origin.startsWith('cross-project:'))
|
|
37
|
+
return 'agent';
|
|
38
|
+
// Unknown origin pattern — treat as agent since something explicit was set,
|
|
39
|
+
// but not matching an auto pattern. Human sources typically have no origin.
|
|
40
|
+
return 'agent';
|
|
41
|
+
}
|
|
9
42
|
function inboxDir(cwd, mode = 'read') {
|
|
10
43
|
return resolveEntityDir('inbox', cwd ?? process.cwd(), mode);
|
|
11
44
|
}
|
|
@@ -39,6 +72,11 @@ export function saveCandidate(candidate, cwd) {
|
|
|
39
72
|
mutate({ cwd }, () => {
|
|
40
73
|
ensureInboxDirs(cwd);
|
|
41
74
|
candidateStore('pending', cwd).save(CandidateSchema.parse(candidate));
|
|
75
|
+
// Auto-refresh live companions after candidate changes (non-fatal)
|
|
76
|
+
try {
|
|
77
|
+
refreshLiveCompanions(cwd);
|
|
78
|
+
}
|
|
79
|
+
catch { /* best-effort */ }
|
|
42
80
|
});
|
|
43
81
|
}
|
|
44
82
|
export function loadCandidate(id, cwd) {
|
|
@@ -47,15 +85,36 @@ export function loadCandidate(id, cwd) {
|
|
|
47
85
|
export function updateCandidate(candidate, cwd) {
|
|
48
86
|
saveCandidate(candidate, cwd);
|
|
49
87
|
}
|
|
50
|
-
export function listCandidates(status, cwd) {
|
|
88
|
+
export function listCandidates(status, cwd, filter) {
|
|
51
89
|
const candidates = candidateStore('pending', cwd).list();
|
|
52
|
-
|
|
90
|
+
const byStatus = status ? candidates.filter((candidate) => candidate.status === status) : candidates;
|
|
91
|
+
return applySourceFilter(byStatus, filter);
|
|
92
|
+
}
|
|
93
|
+
function applySourceFilter(candidates, filter) {
|
|
94
|
+
if (!filter)
|
|
95
|
+
return candidates;
|
|
96
|
+
let result = candidates;
|
|
97
|
+
if (filter.source !== undefined) {
|
|
98
|
+
result = result.filter((c) => resolvedSource(c) === filter.source);
|
|
99
|
+
}
|
|
100
|
+
if (filter.auto_generated === false) {
|
|
101
|
+
result = result.filter((c) => resolvedSource(c) !== 'auto');
|
|
102
|
+
}
|
|
103
|
+
else if (filter.auto_generated === true) {
|
|
104
|
+
result = result.filter((c) => resolvedSource(c) === 'auto');
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
53
107
|
}
|
|
54
108
|
export function archiveCandidate(candidate, dest, cwd) {
|
|
55
109
|
mutate({ cwd }, () => {
|
|
56
110
|
ensureInboxDirs(cwd);
|
|
57
111
|
candidateStore(dest, cwd).save(CandidateSchema.parse(candidate));
|
|
58
112
|
candidateStore('pending', cwd).delete(candidate.id);
|
|
113
|
+
// Auto-refresh live companions after candidate archive (non-fatal)
|
|
114
|
+
try {
|
|
115
|
+
refreshLiveCompanions(cwd);
|
|
116
|
+
}
|
|
117
|
+
catch { /* best-effort */ }
|
|
59
118
|
});
|
|
60
119
|
}
|
|
61
120
|
export function listArchivedCandidates(dest, cwd) {
|
|
@@ -70,6 +129,38 @@ export function deleteArchivedCandidate(id, dest, cwd) {
|
|
|
70
129
|
}
|
|
71
130
|
return false;
|
|
72
131
|
}
|
|
132
|
+
export function cleanupStaleCandidates(options = {}) {
|
|
133
|
+
const maxAgeDays = options.maxAgeDays ?? 30;
|
|
134
|
+
const source = options.source ?? 'auto';
|
|
135
|
+
const cutoffMs = Date.now() - maxAgeDays * 86_400_000;
|
|
136
|
+
const candidates = listCandidates('pending', options.cwd).filter((candidate) => {
|
|
137
|
+
if (resolvedSource(candidate) !== source)
|
|
138
|
+
return false;
|
|
139
|
+
return Date.parse(candidate.created_at) <= cutoffMs;
|
|
140
|
+
});
|
|
141
|
+
if (options.dryRun || candidates.length === 0) {
|
|
142
|
+
return {
|
|
143
|
+
matched: candidates.length,
|
|
144
|
+
deleted: 0,
|
|
145
|
+
candidates,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
mutate({ cwd: options.cwd }, () => {
|
|
149
|
+
const store = candidateStore('pending', options.cwd);
|
|
150
|
+
for (const candidate of candidates) {
|
|
151
|
+
store.delete(candidate.id);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
refreshLiveCompanions(options.cwd);
|
|
155
|
+
}
|
|
156
|
+
catch { /* best-effort */ }
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
matched: candidates.length,
|
|
160
|
+
deleted: candidates.length,
|
|
161
|
+
candidates,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
73
164
|
export function addCandidateStar(id, by, cwd) {
|
|
74
165
|
const candidate = loadCandidate(id, cwd);
|
|
75
166
|
if (candidate.status !== 'pending') {
|