@stackmemoryai/stackmemory 1.10.5 → 1.14.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 +131 -64
- package/README.md +107 -24
- package/bin/claude-sm +16 -1
- package/bin/claude-smd +16 -1
- package/bin/codex-smd +16 -1
- package/bin/gemini-sm +16 -1
- package/bin/hermes-sm +21 -0
- package/bin/hermes-smd +21 -0
- package/bin/opencode-sm +16 -1
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +225 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/brain.js +206 -0
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/company-os.js +184 -0
- package/dist/src/cli/commands/context.js +5 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/operator.js +127 -0
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +232 -5
- package/dist/src/cli/commands/orchestrator.js +315 -26
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/patterns.js +254 -0
- package/dist/src/cli/commands/portal.js +161 -0
- package/dist/src/cli/commands/scaffold.js +92 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +178 -11
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/commands/sync.js +253 -0
- package/dist/src/cli/commands/tasks.js +130 -1
- package/dist/src/cli/commands/vision.js +221 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/hermes-sm.js +224 -0
- package/dist/src/cli/index.js +105 -39
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +116 -0
- package/dist/src/core/brain/brain-store.js +187 -0
- package/dist/src/core/brain/brain-sync.js +193 -0
- package/dist/src/core/brain/index.js +78 -0
- package/dist/src/core/brain/types.js +10 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
- package/dist/src/core/cache/token-estimator.js +39 -0
- package/dist/src/core/config/feature-flags.js +2 -6
- package/dist/src/core/context/frame-database.js +79 -27
- package/dist/src/core/context/recursive-context-manager.js +1 -1
- package/dist/src/core/context/rehydration.js +2 -1
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/cross-search/index.js +10 -0
- package/dist/src/core/database/sqlite-adapter.js +14 -84
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +54 -2
- package/dist/src/core/models/provider-pricing.js +58 -4
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/patterns/index.js +22 -0
- package/dist/src/core/patterns/pattern-applier.js +39 -0
- package/dist/src/core/patterns/pattern-observer.js +157 -0
- package/dist/src/core/patterns/pattern-store.js +259 -0
- package/dist/src/core/patterns/types.js +19 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
- package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +79 -0
- package/dist/src/core/storage/cloud-sync-manager.js +116 -0
- package/dist/src/core/storage/cloud-sync.js +574 -0
- package/dist/src/core/storage/two-tier-storage.js +5 -1
- package/dist/src/core/tasks/master-tasks-template.js +43 -0
- package/dist/src/core/tasks/md-task-parser.js +138 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/vision/index.js +27 -0
- package/dist/src/core/vision/signals.js +79 -0
- package/dist/src/core/vision/types.js +22 -0
- package/dist/src/core/vision/vision-file.js +146 -0
- package/dist/src/core/vision/vision-loop.js +220 -0
- package/dist/src/core/wiki/wiki-compiler.js +103 -1
- package/dist/src/daemon/daemon-config.js +52 -0
- package/dist/src/daemon/services/desire-path-service.js +566 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/services/research-stream-service.js +320 -0
- package/dist/src/daemon/services/telemetry-service.js +192 -0
- package/dist/src/daemon/unified-daemon.js +58 -1
- package/dist/src/features/browser/cli-browser-agent.js +417 -0
- package/dist/src/features/browser/stagehand-workflows.js +578 -0
- package/dist/src/features/operator/adapter-factory.js +62 -0
- package/dist/src/features/operator/browser-adapter.js +109 -0
- package/dist/src/features/operator/desktop-adapter.js +125 -0
- package/dist/src/features/operator/index.js +39 -0
- package/dist/src/features/operator/llm-decision.js +137 -0
- package/dist/src/features/operator/operator-logger.js +92 -0
- package/dist/src/features/operator/overnight-runner.js +327 -0
- package/dist/src/features/operator/screen-adapter.js +91 -0
- package/dist/src/features/operator/session-manager.js +127 -0
- package/dist/src/features/operator/state-machine.js +227 -0
- package/dist/src/features/operator/task-queue.js +81 -0
- package/dist/src/features/portal/index.js +26 -0
- package/dist/src/features/portal/server.js +240 -0
- package/dist/src/features/portal/types.js +14 -0
- package/dist/src/features/portal/ui.js +195 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/features/tasks/task-aware-context.js +2 -1
- package/dist/src/features/tui/simple-monitor.js +0 -23
- package/dist/src/features/tui/swarm-monitor.js +8 -66
- package/dist/src/features/web/client/hooks/use-socket.js +12 -0
- package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
- package/dist/src/features/web/client/next-env.d.js +4 -0
- package/dist/src/features/web/client/stores/session-store.js +12 -0
- package/dist/src/features/web/server/gcp-billing.js +76 -0
- package/dist/src/features/web/server/index.js +10 -0
- package/dist/src/features/web/server/spend-calculator.js +228 -0
- package/dist/src/hooks/schemas.js +6 -1
- package/dist/src/integrations/anthropic/client.js +3 -2
- package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
- package/dist/src/integrations/claude-code/subagent-client.js +307 -11
- package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/linear/webhook-retry.js +196 -0
- package/dist/src/integrations/linear/webhook-server.js +18 -22
- package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -84
- package/dist/src/integrations/mcp/server.js +542 -641
- package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
- package/dist/src/integrations/mcp/tool-definitions.js +152 -682
- package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
- package/dist/src/orchestrators/multimodal/determinism.js +244 -0
- package/dist/src/orchestrators/multimodal/harness.js +149 -78
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
- package/dist/src/utils/hook-installer.js +0 -8
- package/dist/src/utils/process-cleanup.js +1 -7
- package/docs/README.md +42 -0
- package/docs/guides/README_INSTALL.md +208 -0
- package/package.json +27 -9
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/scripts/claude-code-wrapper.sh +11 -0
- package/scripts/claude-sm-setup.sh +12 -1
- package/scripts/codex-wrapper.sh +11 -0
- package/scripts/git-hooks/branch-context-manager.sh +11 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
- package/scripts/hooks/cleanup-shell.sh +12 -1
- package/scripts/hooks/task-complete.sh +12 -1
- package/scripts/install-code-execution-hooks.sh +12 -1
- package/scripts/install-sweep-hook.sh +12 -0
- package/scripts/install.sh +11 -0
- package/scripts/opencode-wrapper.sh +11 -0
- package/scripts/portal/cloud-init.yaml +69 -0
- package/scripts/portal/setup.sh +69 -0
- package/scripts/portal/stackmemory-portal.service +34 -0
- package/scripts/setup-claude-integration.sh +12 -1
- package/scripts/smoke-init-db.sh +23 -0
- package/scripts/stackmemory-daemon.sh +11 -0
- package/scripts/verify-dist.cjs +11 -4
- package/dist/src/cli/commands/ralph.js +0 -1053
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/hooks/diffmem-hooks.js +0 -376
- package/dist/src/integrations/diffmem/client.js +0 -208
- package/dist/src/integrations/diffmem/config.js +0 -14
- package/dist/src/integrations/greptile/client.js +0 -101
- package/dist/src/integrations/greptile/config.js +0 -14
- package/dist/src/integrations/greptile/index.js +0 -11
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
- package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
- package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
- package/dist/src/integrations/ralph/index.js +0 -17
- package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
- package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
- package/dist/src/integrations/ralph/loopmax.js +0 -488
- package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
- package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
- package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
- package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
- package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
- package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
- package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
- package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
- package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
- package/scripts/ralph-loop-implementation.js +0 -404
- /package/dist/src/core/{merge → cache}/types.js +0 -0
- /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
- /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
- /package/dist/src/{integrations/ralph → features/operator}/types.js +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { openBrain } from "../../core/brain/index.js";
|
|
8
|
+
function fmtEntry(e, verbose = false) {
|
|
9
|
+
const id = chalk.dim(e.entryId.slice(0, 8));
|
|
10
|
+
const kind = chalk.cyan(e.kind.padEnd(10));
|
|
11
|
+
const agent = chalk.magenta(`@${e.agent}`);
|
|
12
|
+
const when = new Date(e.createdAt).toISOString().slice(0, 10);
|
|
13
|
+
const head = `${id} ${kind} ${agent} ${chalk.gray(when)} ${chalk.bold(e.title)}`;
|
|
14
|
+
if (!verbose) {
|
|
15
|
+
const concl = e.conclusion ? `
|
|
16
|
+
${chalk.green("\u2192")} ${e.conclusion}` : "";
|
|
17
|
+
return head + concl;
|
|
18
|
+
}
|
|
19
|
+
const lines = [head];
|
|
20
|
+
if (e.summary) lines.push(` ${chalk.gray("summary:")} ${e.summary}`);
|
|
21
|
+
if (e.conclusion)
|
|
22
|
+
lines.push(` ${chalk.green("conclusion:")} ${e.conclusion}`);
|
|
23
|
+
if (e.tags.length)
|
|
24
|
+
lines.push(` ${chalk.gray("tags:")} ${e.tags.join(", ")}`);
|
|
25
|
+
if (e.refs.length)
|
|
26
|
+
lines.push(` ${chalk.gray("refs:")} ${e.refs.join(", ")}`);
|
|
27
|
+
lines.push(` ${chalk.gray("confidence:")} ${e.confidence}`);
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
function createBrainCommand() {
|
|
31
|
+
const cmd = new Command("brain").description("Shared, compounding context state (per repo + org)").addHelpText(
|
|
32
|
+
"after",
|
|
33
|
+
`
|
|
34
|
+
Examples:
|
|
35
|
+
stackmemory brain record --kind experiment \\
|
|
36
|
+
--title "Retry with jitter cut 5xx" \\
|
|
37
|
+
--summary "Tried exp backoff + jitter on the sync client" \\
|
|
38
|
+
--conclusion "p99 errors dropped 60%; ship it" --tags sync,reliability
|
|
39
|
+
stackmemory brain recall "retry" Search this repo's brain
|
|
40
|
+
stackmemory brain recall "auth" --org Search the whole org
|
|
41
|
+
stackmemory brain list --limit 10
|
|
42
|
+
stackmemory brain show <id>
|
|
43
|
+
stackmemory brain sync Push + pull online
|
|
44
|
+
stackmemory brain status
|
|
45
|
+
|
|
46
|
+
Every agent (Claude, Codex, OpenCode, Hermes) shares this brain \u2014 log
|
|
47
|
+
experiment conclusions so mutual thinking compounds. See docs/guides/BRAIN.md.
|
|
48
|
+
`
|
|
49
|
+
);
|
|
50
|
+
cmd.command("record").description("Record an experiment / decision / insight / note").option("--title <title>", "Short title (required)").option("--summary <text>", "What was done / context").option("--conclusion <text>", "What was concluded (the payload)").option("--kind <kind>", "experiment | decision | insight | note", "note").option("--agent <name>", "Agent that produced this", "claude").option("--tags <tags>", "Comma-separated tags").option("--refs <refs>", "Comma-separated refs (issues, commits, files)").option("--confidence <n>", "Confidence 0..1", "0.7").option("--json", "Output as JSON").action((options) => {
|
|
51
|
+
if (!options.title) {
|
|
52
|
+
console.error(chalk.red("--title is required"));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const ctx = openBrain();
|
|
56
|
+
try {
|
|
57
|
+
const entry = ctx.store.record({
|
|
58
|
+
title: options.title,
|
|
59
|
+
summary: options.summary,
|
|
60
|
+
conclusion: options.conclusion,
|
|
61
|
+
kind: options.kind,
|
|
62
|
+
agent: options.agent,
|
|
63
|
+
tags: splitList(options.tags),
|
|
64
|
+
refs: splitList(options.refs),
|
|
65
|
+
confidence: parseFloat(options.confidence)
|
|
66
|
+
});
|
|
67
|
+
if (options.json) {
|
|
68
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
69
|
+
} else {
|
|
70
|
+
console.log(
|
|
71
|
+
chalk.green("\u2713 recorded"),
|
|
72
|
+
chalk.dim(entry.entryId.slice(0, 8))
|
|
73
|
+
);
|
|
74
|
+
console.log(fmtEntry(entry));
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
ctx.close();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
cmd.command("recall").description("Search the brain (this repo by default, --org for the org)").argument("[query]", "Free-text query").option("--org", "Search across the whole org (all repos)").option("--agent <name>", "Filter by agent").option("--kind <kind>", "Filter by kind").option("--limit <n>", "Max results", "20").option("--all", "Include superseded entries").option("--json", "Output as JSON").action((query, options) => {
|
|
81
|
+
const ctx = openBrain();
|
|
82
|
+
try {
|
|
83
|
+
const q = {
|
|
84
|
+
org: !!options.org,
|
|
85
|
+
limit: parseInt(options.limit, 10),
|
|
86
|
+
includeSuperseded: !!options.all,
|
|
87
|
+
...query ? { text: query } : {},
|
|
88
|
+
...options.agent ? { agent: options.agent } : {},
|
|
89
|
+
...options.kind ? { kind: options.kind } : {}
|
|
90
|
+
};
|
|
91
|
+
const results = ctx.store.recall(q);
|
|
92
|
+
if (options.json) {
|
|
93
|
+
console.log(JSON.stringify(results, null, 2));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (results.length === 0) {
|
|
97
|
+
console.log(chalk.yellow("No matching brain entries."));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const scope = options.org ? "org" : "repo";
|
|
101
|
+
console.log(chalk.bold(`${results.length} result(s) [${scope}]`));
|
|
102
|
+
for (const e of results) console.log("\n" + fmtEntry(e));
|
|
103
|
+
} finally {
|
|
104
|
+
ctx.close();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
cmd.command("list").description("List recent brain entries for this repo").option("--limit <n>", "Max results", "20").option("--json", "Output as JSON").action((options) => {
|
|
108
|
+
const ctx = openBrain();
|
|
109
|
+
try {
|
|
110
|
+
const results = ctx.store.recall({
|
|
111
|
+
limit: parseInt(options.limit, 10)
|
|
112
|
+
});
|
|
113
|
+
if (options.json) {
|
|
114
|
+
console.log(JSON.stringify(results, null, 2));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (results.length === 0) {
|
|
118
|
+
console.log(chalk.yellow("Brain is empty for this repo."));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
for (const e of results) console.log(fmtEntry(e) + "\n");
|
|
122
|
+
} finally {
|
|
123
|
+
ctx.close();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
cmd.command("show").description("Show a single entry in full").argument("<id>", "Entry id (or prefix)").option("--json", "Output as JSON").action((id, options) => {
|
|
127
|
+
const ctx = openBrain();
|
|
128
|
+
try {
|
|
129
|
+
const entry = ctx.store.get(id);
|
|
130
|
+
if (!entry) {
|
|
131
|
+
console.error(chalk.red(`No entry matching '${id}'`));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
console.log(
|
|
135
|
+
options.json ? JSON.stringify(entry, null, 2) : fmtEntry(entry, true)
|
|
136
|
+
);
|
|
137
|
+
} finally {
|
|
138
|
+
ctx.close();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
cmd.command("sync").description("Push + pull brain entries online").option("--push", "Push only").option("--pull", "Pull only").option("--json", "Output as JSON").action(async (options) => {
|
|
142
|
+
const ctx = openBrain();
|
|
143
|
+
try {
|
|
144
|
+
if (!ctx.sync) {
|
|
145
|
+
console.error(
|
|
146
|
+
chalk.yellow(
|
|
147
|
+
"Online brain not configured. Run `stackmemory login`."
|
|
148
|
+
)
|
|
149
|
+
);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
const result = options.push ? await ctx.sync.push() : options.pull ? await ctx.sync.pull() : await ctx.sync.sync();
|
|
153
|
+
if (options.json) {
|
|
154
|
+
console.log(JSON.stringify(result, null, 2));
|
|
155
|
+
} else if (result.success) {
|
|
156
|
+
console.log(
|
|
157
|
+
chalk.green(
|
|
158
|
+
`\u2713 pushed ${result.pushed}, pulled ${result.pulled} (applied ${result.applied})`
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
} else {
|
|
162
|
+
console.error(chalk.red(`Sync failed: ${result.error}`));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
} finally {
|
|
166
|
+
ctx.close();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
cmd.command("status").description("Show brain scope + entry counts").option("--json", "Output as JSON").action((options) => {
|
|
170
|
+
const ctx = openBrain();
|
|
171
|
+
try {
|
|
172
|
+
const status = {
|
|
173
|
+
projectId: ctx.projectId,
|
|
174
|
+
workspaceId: ctx.workspaceId || null,
|
|
175
|
+
repoEntries: ctx.store.count(false),
|
|
176
|
+
orgEntries: ctx.workspaceId ? ctx.store.count(true) : 0,
|
|
177
|
+
online: !!ctx.sync
|
|
178
|
+
};
|
|
179
|
+
if (options.json) {
|
|
180
|
+
console.log(JSON.stringify(status, null, 2));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
console.log(chalk.bold("Brain Status"));
|
|
184
|
+
console.log(` Repo (project): ${status.projectId}`);
|
|
185
|
+
console.log(
|
|
186
|
+
` Org (workspace): ${status.workspaceId ?? chalk.dim("not logged in")}`
|
|
187
|
+
);
|
|
188
|
+
console.log(` Repo entries: ${status.repoEntries}`);
|
|
189
|
+
if (ctx.workspaceId)
|
|
190
|
+
console.log(` Org entries: ${status.orgEntries}`);
|
|
191
|
+
console.log(
|
|
192
|
+
` Online sync: ${status.online ? chalk.green("configured") : chalk.dim("local-only")}`
|
|
193
|
+
);
|
|
194
|
+
} finally {
|
|
195
|
+
ctx.close();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return cmd;
|
|
199
|
+
}
|
|
200
|
+
function splitList(v) {
|
|
201
|
+
if (!v) return [];
|
|
202
|
+
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
203
|
+
}
|
|
204
|
+
export {
|
|
205
|
+
createBrainCommand
|
|
206
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { existsSync } from "fs";
|
|
9
|
+
import Database from "better-sqlite3";
|
|
10
|
+
import { ContentCache } from "../../core/cache/index.js";
|
|
11
|
+
function findProjectDbPath() {
|
|
12
|
+
let dir = process.cwd();
|
|
13
|
+
while (dir !== "/") {
|
|
14
|
+
const dbPath = path.join(dir, ".stackmemory", "context.db");
|
|
15
|
+
if (existsSync(dbPath)) return dbPath;
|
|
16
|
+
dir = path.dirname(dir);
|
|
17
|
+
}
|
|
18
|
+
const home = process.env["HOME"] || "/tmp";
|
|
19
|
+
const homePath = path.join(home, ".stackmemory", "context.db");
|
|
20
|
+
if (existsSync(homePath)) return homePath;
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
function getProjectDb() {
|
|
24
|
+
const dbPath = findProjectDbPath();
|
|
25
|
+
if (!dbPath) return void 0;
|
|
26
|
+
return new Database(dbPath);
|
|
27
|
+
}
|
|
28
|
+
function createCacheCommand() {
|
|
29
|
+
const cmd = new Command("cache").description(
|
|
30
|
+
"Content-hash cache \u2014 view token savings and manage cached content"
|
|
31
|
+
);
|
|
32
|
+
cmd.command("stats").description("Show cache statistics and token savings").option("--json", "Output as JSON").action((options) => {
|
|
33
|
+
const db = getProjectDb();
|
|
34
|
+
if (!db) {
|
|
35
|
+
console.log(chalk.yellow("No project database found"));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const cache = new ContentCache(db);
|
|
40
|
+
const stats = cache.getStats();
|
|
41
|
+
if (options.json) {
|
|
42
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(chalk.bold("Content Cache Statistics\n"));
|
|
46
|
+
console.log(` Entries: ${stats.totalEntries.toLocaleString()}`);
|
|
47
|
+
console.log(
|
|
48
|
+
` Tokens cached: ${stats.totalTokensCached.toLocaleString()}`
|
|
49
|
+
);
|
|
50
|
+
console.log(
|
|
51
|
+
` Tokens saved: ${chalk.green(stats.totalTokensSaved.toLocaleString())}`
|
|
52
|
+
);
|
|
53
|
+
console.log(` Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);
|
|
54
|
+
if (stats.topSources.length > 0) {
|
|
55
|
+
console.log(chalk.bold("\n Top sources by tokens saved:"));
|
|
56
|
+
for (const src of stats.topSources.slice(0, 5)) {
|
|
57
|
+
console.log(
|
|
58
|
+
` ${chalk.dim(src.source)}: ${src.tokensSaved.toLocaleString()} tokens`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const costSaved = stats.totalTokensSaved / 1e6 * 3;
|
|
63
|
+
if (costSaved > 0.01) {
|
|
64
|
+
console.log(
|
|
65
|
+
`
|
|
66
|
+
Est. cost saved: ${chalk.green("$" + costSaved.toFixed(2))}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
} finally {
|
|
70
|
+
db.close();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
cmd.command("clear").description("Clear the content cache").option("--confirm", "Skip confirmation prompt").action((options) => {
|
|
74
|
+
if (!options.confirm) {
|
|
75
|
+
console.log(
|
|
76
|
+
chalk.yellow(
|
|
77
|
+
"This will clear all cached content. Run with --confirm to proceed."
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const db = getProjectDb();
|
|
83
|
+
if (!db) {
|
|
84
|
+
console.log(chalk.yellow("No project database found"));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const cache = new ContentCache(db);
|
|
89
|
+
cache.clear();
|
|
90
|
+
console.log(chalk.green("Cache cleared"));
|
|
91
|
+
} finally {
|
|
92
|
+
db.close();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
cmd.command("search <query>").description("Search cached content by keyword").option("--limit <n>", "Max results", "10").action((query, options) => {
|
|
96
|
+
const db = getProjectDb();
|
|
97
|
+
if (!db) {
|
|
98
|
+
console.log(chalk.yellow("No project database found"));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const cache = new ContentCache(db);
|
|
103
|
+
const results = cache.search(query, parseInt(options.limit));
|
|
104
|
+
if (results.length === 0) {
|
|
105
|
+
console.log(chalk.dim(`No cached content matching "${query}"`));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(chalk.bold(`${results.length} result(s):
|
|
109
|
+
`));
|
|
110
|
+
for (const entry of results) {
|
|
111
|
+
const preview = entry.content.slice(0, 120).replace(/\n/g, " ");
|
|
112
|
+
console.log(
|
|
113
|
+
` ${chalk.dim(entry.hash.slice(0, 8))} ${chalk.dim(`(${entry.tokenCount} tokens, ${entry.hitCount} hits)`)}`
|
|
114
|
+
);
|
|
115
|
+
console.log(` ${preview}${entry.content.length > 120 ? "..." : ""}`);
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
db.close();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return cmd;
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
createCacheCommand
|
|
126
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
3
|
+
import { dirname as __pathDirname } from 'path';
|
|
4
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = __pathDirname(__filename);
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
9
|
+
import { join, extname } from "node:path";
|
|
10
|
+
const WIKI_DIR = join(process.cwd(), "wiki");
|
|
11
|
+
const WIKI_SOPS_DIR = join(WIKI_DIR, "sops");
|
|
12
|
+
const PROSE_SPEC_PATH = join(
|
|
13
|
+
process.cwd(),
|
|
14
|
+
"docs",
|
|
15
|
+
"specs",
|
|
16
|
+
"COMPANY-OS-PROSE.md"
|
|
17
|
+
);
|
|
18
|
+
function collectSopFiles() {
|
|
19
|
+
const files = [];
|
|
20
|
+
const dirs = [WIKI_DIR];
|
|
21
|
+
if (existsSync(WIKI_SOPS_DIR)) dirs.push(WIKI_SOPS_DIR);
|
|
22
|
+
for (const dir of dirs) {
|
|
23
|
+
if (!existsSync(dir)) continue;
|
|
24
|
+
for (const entry of readdirSync(dir)) {
|
|
25
|
+
if (extname(entry) !== ".md") continue;
|
|
26
|
+
const filePath = join(dir, entry);
|
|
27
|
+
const content = readFileSync(filePath, "utf8");
|
|
28
|
+
const idMatch = content.match(/^# (SOP-\d+)/m);
|
|
29
|
+
const titleMatch = content.match(/^# SOP-\d+\s+(.+)/m);
|
|
30
|
+
const statusMatch = content.match(/\*\*Status:\*\*\s*(\w+)/);
|
|
31
|
+
const proseMatch = content.match(/Related PROSE Expectation.*\[(E\.\d+)/);
|
|
32
|
+
files.push({
|
|
33
|
+
id: idMatch?.[1] ?? entry.replace(".md", ""),
|
|
34
|
+
title: titleMatch?.[1] ?? entry,
|
|
35
|
+
status: statusMatch?.[1] ?? "Unknown",
|
|
36
|
+
proseId: proseMatch?.[1] ?? null,
|
|
37
|
+
path: filePath
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
function loadValidProseIds() {
|
|
44
|
+
const ids = /* @__PURE__ */ new Set();
|
|
45
|
+
if (!existsSync(PROSE_SPEC_PATH)) return ids;
|
|
46
|
+
const content = readFileSync(PROSE_SPEC_PATH, "utf8");
|
|
47
|
+
const headingRegex = /^###\s+(E\.\d+)\s+/gm;
|
|
48
|
+
let match;
|
|
49
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
50
|
+
ids.add(match[1]);
|
|
51
|
+
}
|
|
52
|
+
return ids;
|
|
53
|
+
}
|
|
54
|
+
function validateSop(content) {
|
|
55
|
+
const errors = [];
|
|
56
|
+
const required = ["## Objective", "## Procedure", "## Verification"];
|
|
57
|
+
for (const section of required) {
|
|
58
|
+
if (!content.includes(section)) {
|
|
59
|
+
errors.push(`Missing section: ${section}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!content.match(/Related PROSE Expectation.*\[E\.\d+/)) {
|
|
63
|
+
errors.push("Missing or invalid Related PROSE Expectation");
|
|
64
|
+
}
|
|
65
|
+
if (!content.match(/^# SOP-\d+/m)) {
|
|
66
|
+
errors.push("Missing or invalid SOP ID (expected # SOP-NNN ...)");
|
|
67
|
+
}
|
|
68
|
+
return errors;
|
|
69
|
+
}
|
|
70
|
+
function createCompanyOsCommand() {
|
|
71
|
+
const cmd = new Command("company-os").alias("cos").description("Manage Company OS processes, SOPs, and audits");
|
|
72
|
+
cmd.command("list").description("List SOPs in the Company OS wiki").option("--json", "Output as JSON").action(() => {
|
|
73
|
+
const sops = collectSopFiles();
|
|
74
|
+
if (sops.length === 0) {
|
|
75
|
+
console.log(chalk.yellow("No SOPs found in wiki/"));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
console.log(chalk.cyan(`
|
|
79
|
+
Company OS SOPs (${sops.length})
|
|
80
|
+
`));
|
|
81
|
+
for (const sop of sops.sort((a, b) => a.id.localeCompare(b.id))) {
|
|
82
|
+
console.log(
|
|
83
|
+
` ${chalk.bold(sop.id)} ${chalk.white(sop.title.slice(0, 50))}`
|
|
84
|
+
);
|
|
85
|
+
console.log(
|
|
86
|
+
chalk.gray(
|
|
87
|
+
` status: ${sop.status} PROSE: ${sop.proseId ?? "none"} ${sop.path}`
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
console.log();
|
|
92
|
+
});
|
|
93
|
+
cmd.command("validate").description("Validate all Company OS SOPs against the schema").option("--json", "Output as JSON").action(() => {
|
|
94
|
+
const sops = collectSopFiles();
|
|
95
|
+
const validProseIds = loadValidProseIds();
|
|
96
|
+
const results = [];
|
|
97
|
+
for (const sop of sops) {
|
|
98
|
+
const content = readFileSync(sop.path, "utf8");
|
|
99
|
+
const errors = validateSop(content);
|
|
100
|
+
if (sop.proseId && !validProseIds.has(sop.proseId)) {
|
|
101
|
+
errors.push(`Invalid PROSE Expectation: ${sop.proseId}`);
|
|
102
|
+
}
|
|
103
|
+
results.push({
|
|
104
|
+
id: sop.id,
|
|
105
|
+
path: sop.path,
|
|
106
|
+
valid: errors.length === 0,
|
|
107
|
+
errors
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const validCount = results.filter((r) => r.valid).length;
|
|
111
|
+
if (results.every((r) => r.valid)) {
|
|
112
|
+
console.log(chalk.green(`
|
|
113
|
+
\u2713 All ${validCount} SOPs are valid.
|
|
114
|
+
`));
|
|
115
|
+
} else {
|
|
116
|
+
console.log(chalk.yellow(`
|
|
117
|
+
SOP Validation Results
|
|
118
|
+
`));
|
|
119
|
+
for (const result of results) {
|
|
120
|
+
const icon = result.valid ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
121
|
+
console.log(`${icon} ${result.id}`);
|
|
122
|
+
for (const error of result.errors) {
|
|
123
|
+
console.log(chalk.gray(` - ${error}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
console.log(chalk.gray(`
|
|
127
|
+
Valid: ${validCount} / ${results.length}
|
|
128
|
+
`));
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
cmd.command("audit <process>").description(
|
|
133
|
+
"Audit a Company OS process against its SOP (e.g. onboarding, pto)"
|
|
134
|
+
).option("--json", "Output as JSON").action((processName) => {
|
|
135
|
+
const sops = collectSopFiles();
|
|
136
|
+
const normalized = processName.toLowerCase().replace(/\s+/g, "-");
|
|
137
|
+
const sop = sops.find((s) => {
|
|
138
|
+
const sopNormalized = s.title.toLowerCase().replace(/\s+/g, "-");
|
|
139
|
+
return sopNormalized.includes(normalized) || s.id.toLowerCase().includes(normalized);
|
|
140
|
+
});
|
|
141
|
+
if (!sop) {
|
|
142
|
+
console.log(
|
|
143
|
+
chalk.red(`No SOP found matching process "${processName}"`)
|
|
144
|
+
);
|
|
145
|
+
console.log(chalk.gray("Run: stackmemory company-os list"));
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const content = readFileSync(sop.path, "utf8");
|
|
150
|
+
const errors = validateSop(content);
|
|
151
|
+
const validProseIds = loadValidProseIds();
|
|
152
|
+
if (sop.proseId && !validProseIds.has(sop.proseId)) {
|
|
153
|
+
errors.push(`Invalid PROSE Expectation: ${sop.proseId}`);
|
|
154
|
+
}
|
|
155
|
+
const compliant = errors.length === 0;
|
|
156
|
+
console.log(chalk.cyan(`
|
|
157
|
+
Company OS Audit: ${sop.id} ${sop.title}
|
|
158
|
+
`));
|
|
159
|
+
console.log(` PROSE Expectation: ${sop.proseId ?? "none"}`);
|
|
160
|
+
console.log(
|
|
161
|
+
` Status: ${compliant ? chalk.green("compliant") : chalk.red("non-compliant")}`
|
|
162
|
+
);
|
|
163
|
+
if (errors.length > 0) {
|
|
164
|
+
console.log(chalk.yellow("\n Findings:"));
|
|
165
|
+
for (const error of errors) {
|
|
166
|
+
console.log(chalk.gray(` - ${error}`));
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
console.log(
|
|
170
|
+
chalk.gray(
|
|
171
|
+
"\n Note: This POC audit validates SOP structure only. A full audit would check operational data against the SOP."
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
console.log();
|
|
176
|
+
if (!compliant) {
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return cmd;
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
createCompanyOsCommand
|
|
184
|
+
};
|
|
@@ -34,6 +34,7 @@ function createContextCommands() {
|
|
|
34
34
|
const frameManager = new FrameManager(db, projectId, {
|
|
35
35
|
skipContextBridge: true
|
|
36
36
|
});
|
|
37
|
+
await frameManager.initialize();
|
|
37
38
|
const depth = frameManager.getStackDepth();
|
|
38
39
|
const activePath = frameManager.getActiveFramePath();
|
|
39
40
|
console.log(`
|
|
@@ -101,6 +102,7 @@ function createContextCommands() {
|
|
|
101
102
|
const frameManager = new FrameManager(db, projectId, {
|
|
102
103
|
skipContextBridge: true
|
|
103
104
|
});
|
|
105
|
+
await frameManager.initialize();
|
|
104
106
|
const activePath = frameManager.getActiveFramePath();
|
|
105
107
|
const parentId = activePath.length > 0 ? activePath[activePath.length - 1].frame_id : void 0;
|
|
106
108
|
let inputs = {};
|
|
@@ -147,6 +149,7 @@ function createContextCommands() {
|
|
|
147
149
|
const frameManager = new FrameManager(db, projectId, {
|
|
148
150
|
skipContextBridge: true
|
|
149
151
|
});
|
|
152
|
+
await frameManager.initialize();
|
|
150
153
|
const activePath = frameManager.getActiveFramePath();
|
|
151
154
|
if (activePath.length === 0) {
|
|
152
155
|
console.log("\u{1F4DA} Stack is already empty.");
|
|
@@ -193,6 +196,7 @@ function createContextCommands() {
|
|
|
193
196
|
const frameManager = new FrameManager(db, projectId, {
|
|
194
197
|
skipContextBridge: true
|
|
195
198
|
});
|
|
199
|
+
await frameManager.initialize();
|
|
196
200
|
const activePath = frameManager.getActiveFramePath();
|
|
197
201
|
if (activePath.length === 0) {
|
|
198
202
|
console.log("\u26A0\uFE0F No active context frame. Creating one...");
|
|
@@ -248,6 +252,7 @@ function createContextCommands() {
|
|
|
248
252
|
const frameManager = new FrameManager(db, projectId, {
|
|
249
253
|
skipContextBridge: true
|
|
250
254
|
});
|
|
255
|
+
await frameManager.initialize();
|
|
251
256
|
if (options.list || action === "list") {
|
|
252
257
|
const worktreeFrames = db.prepare(
|
|
253
258
|
`
|
|
@@ -107,6 +107,7 @@ The daemon provides:
|
|
|
107
107
|
const services = [];
|
|
108
108
|
if (newStatus.services.context.enabled) services.push("context");
|
|
109
109
|
if (newStatus.services.linear.enabled) services.push("linear");
|
|
110
|
+
if (newStatus.services.github?.enabled) services.push("github");
|
|
110
111
|
if (newStatus.services.maintenance?.enabled)
|
|
111
112
|
services.push("maintenance");
|
|
112
113
|
if (newStatus.services.memory?.enabled) services.push("memory");
|
|
@@ -232,6 +233,25 @@ The daemon provides:
|
|
|
232
233
|
console.log(chalk.gray(` Syncs: ${lin.syncCount}`));
|
|
233
234
|
}
|
|
234
235
|
}
|
|
236
|
+
const gh = status.services.github;
|
|
237
|
+
if (gh) {
|
|
238
|
+
console.log(
|
|
239
|
+
` GitHub: ${gh.enabled ? chalk.green("Enabled") : chalk.gray("Disabled")}`
|
|
240
|
+
);
|
|
241
|
+
if (gh.enabled) {
|
|
242
|
+
console.log(
|
|
243
|
+
chalk.gray(` Interval: ${config.github.interval} min`)
|
|
244
|
+
);
|
|
245
|
+
if (gh.syncCount) {
|
|
246
|
+
console.log(chalk.gray(` Refreshes: ${gh.syncCount}`));
|
|
247
|
+
}
|
|
248
|
+
if (gh.lastProjectionState) {
|
|
249
|
+
console.log(
|
|
250
|
+
chalk.gray(` Last PR state: ${gh.lastProjectionState}`)
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
235
255
|
const maint = status.services.maintenance;
|
|
236
256
|
if (maint) {
|
|
237
257
|
console.log(
|
|
@@ -605,6 +625,22 @@ function getServiceHealthChecks(status, config) {
|
|
|
605
625
|
detail: "Disabled"
|
|
606
626
|
});
|
|
607
627
|
}
|
|
628
|
+
const gh = status.services.github;
|
|
629
|
+
if (gh?.enabled) {
|
|
630
|
+
const intervalMs = config.github.interval * 6e4;
|
|
631
|
+
const overdue = gh.lastRun ? Date.now() - gh.lastRun > intervalMs * 2 : false;
|
|
632
|
+
checks.push({
|
|
633
|
+
name: "GitHub Service",
|
|
634
|
+
status: overdue ? "warn" : "ok",
|
|
635
|
+
detail: gh.lastRun ? `Last refresh: ${formatTimeAgo(gh.lastRun)} | Refreshes: ${gh.syncCount ?? 0}${gh.lastProjectionState ? ` | Last PR state: ${gh.lastProjectionState}` : ""}` : `Enabled (interval: ${config.github.interval}m) | No refreshes yet`
|
|
636
|
+
});
|
|
637
|
+
} else {
|
|
638
|
+
checks.push({
|
|
639
|
+
name: "GitHub Service",
|
|
640
|
+
status: "ok",
|
|
641
|
+
detail: "Disabled"
|
|
642
|
+
});
|
|
643
|
+
}
|
|
608
644
|
const maint = status.services.maintenance;
|
|
609
645
|
if (maint?.enabled) {
|
|
610
646
|
const intervalMs = config.maintenance.interval * 6e4;
|
|
@@ -701,6 +737,11 @@ function buildHealthReport(status, config, paths) {
|
|
|
701
737
|
enabled: status.services.linear.enabled,
|
|
702
738
|
lastRun: status.services.linear.lastRun
|
|
703
739
|
},
|
|
740
|
+
{
|
|
741
|
+
key: "github",
|
|
742
|
+
enabled: status.services.github?.enabled ?? false,
|
|
743
|
+
lastRun: status.services.github?.lastRun
|
|
744
|
+
},
|
|
704
745
|
{
|
|
705
746
|
key: "maintenance",
|
|
706
747
|
enabled: status.services.maintenance?.enabled ?? false,
|