@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,265 @@
|
|
|
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 {
|
|
8
|
+
canonicalStateStore
|
|
9
|
+
} from "../../core/shared-state/canonical-store.js";
|
|
10
|
+
import {
|
|
11
|
+
getCurrentRepoGitHubInfo,
|
|
12
|
+
refreshCurrentRepoPullRequestState
|
|
13
|
+
} from "../../integrations/github/pr-state.js";
|
|
14
|
+
function parseJsonObject(input) {
|
|
15
|
+
if (!input) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
const parsed = JSON.parse(input);
|
|
19
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
20
|
+
throw new Error("Expected a JSON object");
|
|
21
|
+
}
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
function createStateCommand() {
|
|
25
|
+
const cmd = new Command("state").description(
|
|
26
|
+
"Manage canonical user-scoped shared state across instances and sessions"
|
|
27
|
+
);
|
|
28
|
+
const instance = cmd.command("instance").description("Manage instance state");
|
|
29
|
+
instance.command("upsert").requiredOption("--id <id>", "Instance identifier").requiredOption(
|
|
30
|
+
"--tool <tool>",
|
|
31
|
+
"Tool name (claude|codex|opencode|stackmemory)"
|
|
32
|
+
).option("--session <id>", "Session identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--worktree-path <path>", "Worktree path").option("--pid <pid>", "Process id").option("--status <status>", "Status", "active").option("--metadata <json>", "Metadata JSON object").action(async (options) => {
|
|
33
|
+
const record = await canonicalStateStore.upsertInstance({
|
|
34
|
+
instanceId: options.id,
|
|
35
|
+
tool: options.tool,
|
|
36
|
+
sessionId: options.session,
|
|
37
|
+
projectId: options.project,
|
|
38
|
+
projectPath: options.projectPath,
|
|
39
|
+
branch: options.branch,
|
|
40
|
+
worktreePath: options.worktreePath,
|
|
41
|
+
pid: options.pid ? Number(options.pid) : void 0,
|
|
42
|
+
status: options.status,
|
|
43
|
+
metadata: parseJsonObject(options.metadata)
|
|
44
|
+
});
|
|
45
|
+
console.log(JSON.stringify(record, null, 2));
|
|
46
|
+
});
|
|
47
|
+
instance.command("end").requiredOption("--id <id>", "Instance identifier").action(async (options) => {
|
|
48
|
+
await canonicalStateStore.endInstance(options.id);
|
|
49
|
+
console.log(chalk.green(`Ended instance ${options.id}`));
|
|
50
|
+
});
|
|
51
|
+
const session = cmd.command("session").description("Manage session state");
|
|
52
|
+
session.command("upsert").requiredOption("--id <id>", "Session identifier").requiredOption(
|
|
53
|
+
"--tool <tool>",
|
|
54
|
+
"Tool name (claude|codex|opencode|stackmemory)"
|
|
55
|
+
).option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--instance <id>", "Associated instance identifier").option("--status <status>", "Status", "active").option("--metadata <json>", "Metadata JSON object").action(async (options) => {
|
|
56
|
+
const record = await canonicalStateStore.upsertSession({
|
|
57
|
+
sessionId: options.id,
|
|
58
|
+
tool: options.tool,
|
|
59
|
+
projectId: options.project,
|
|
60
|
+
projectPath: options.projectPath,
|
|
61
|
+
branch: options.branch,
|
|
62
|
+
instanceId: options.instance,
|
|
63
|
+
status: options.status,
|
|
64
|
+
metadata: parseJsonObject(options.metadata)
|
|
65
|
+
});
|
|
66
|
+
console.log(JSON.stringify(record, null, 2));
|
|
67
|
+
});
|
|
68
|
+
session.command("end").requiredOption("--id <id>", "Session identifier").option("--status <status>", "Status", "closed").action(async (options) => {
|
|
69
|
+
await canonicalStateStore.endSession(options.id, options.status);
|
|
70
|
+
console.log(chalk.green(`Ended session ${options.id}`));
|
|
71
|
+
});
|
|
72
|
+
cmd.command("event").description("Append a shared-state event").requiredOption("--type <type>", "Event type").option("--tool <tool>", "Tool name (claude|codex|opencode|stackmemory)").option("--instance <id>", "Instance identifier").option("--session <id>", "Session identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--payload <json>", "Payload JSON object").action(async (options) => {
|
|
73
|
+
const event = await canonicalStateStore.appendEvent({
|
|
74
|
+
type: options.type,
|
|
75
|
+
tool: options.tool,
|
|
76
|
+
instanceId: options.instance,
|
|
77
|
+
sessionId: options.session,
|
|
78
|
+
projectId: options.project,
|
|
79
|
+
projectPath: options.projectPath,
|
|
80
|
+
branch: options.branch,
|
|
81
|
+
payload: parseJsonObject(options.payload)
|
|
82
|
+
});
|
|
83
|
+
console.log(JSON.stringify(event, null, 2));
|
|
84
|
+
});
|
|
85
|
+
cmd.command("show").description("Show canonical shared state for a project").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--limit <count>", "Recent event limit", "10").option("--json", "Emit JSON output").action(async (options) => {
|
|
86
|
+
const summary = await canonicalStateStore.getProjectSummary({
|
|
87
|
+
projectId: options.project,
|
|
88
|
+
projectPath: options.projectPath,
|
|
89
|
+
eventLimit: Number(options.limit)
|
|
90
|
+
});
|
|
91
|
+
if (options.json) {
|
|
92
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log(chalk.bold("Canonical Shared State"));
|
|
96
|
+
console.log(
|
|
97
|
+
` Active sessions: ${summary.activeSessions.length} | Active instances: ${summary.activeInstances.length} | Active claims: ${summary.activeClaims.length}`
|
|
98
|
+
);
|
|
99
|
+
if (summary.projectId) {
|
|
100
|
+
console.log(` Project: ${summary.projectId}`);
|
|
101
|
+
}
|
|
102
|
+
if (summary.activeSessions.length > 0) {
|
|
103
|
+
console.log(chalk.bold("\nSessions"));
|
|
104
|
+
for (const record of summary.activeSessions) {
|
|
105
|
+
console.log(
|
|
106
|
+
` ${record.sessionId.slice(0, 8)} ${record.tool} ${record.branch || ""}`.trim()
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (summary.activeInstances.length > 0) {
|
|
111
|
+
console.log(chalk.bold("\nInstances"));
|
|
112
|
+
for (const record of summary.activeInstances) {
|
|
113
|
+
console.log(
|
|
114
|
+
` ${record.instanceId} ${record.tool} ${record.branch || ""}`.trim()
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (summary.activeClaims.length > 0) {
|
|
119
|
+
console.log(chalk.bold("\nClaims"));
|
|
120
|
+
for (const claim of summary.activeClaims) {
|
|
121
|
+
const scopes = [
|
|
122
|
+
claim.branch ? `branch:${claim.branch}` : "",
|
|
123
|
+
...claim.paths.map((item) => `path:${item}`)
|
|
124
|
+
].filter(Boolean).join(", ");
|
|
125
|
+
console.log(
|
|
126
|
+
` ${claim.claimId.slice(0, 8)} ${claim.tool} ${scopes || "(no scope)"}`.trim()
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (summary.recentEvents.length > 0) {
|
|
131
|
+
console.log(chalk.bold("\nRecent events"));
|
|
132
|
+
for (const event of summary.recentEvents) {
|
|
133
|
+
console.log(
|
|
134
|
+
` ${event.type} ${new Date(event.timestamp).toISOString()}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const claims = cmd.command("claims").description("Manage shared ownership claims");
|
|
140
|
+
claims.command("claim").requiredOption(
|
|
141
|
+
"--tool <tool>",
|
|
142
|
+
"Tool name (claude|codex|opencode|stackmemory)"
|
|
143
|
+
).option("--session <id>", "Session identifier").option("--instance <id>", "Instance identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--path <path...>", "Claimed file or directory path(s)").option("--ttl-ms <ttl>", "Time to live in milliseconds", "86400000").option("--metadata <json>", "Metadata JSON object").option("--json", "Emit JSON output").action(async (options) => {
|
|
144
|
+
const result = await canonicalStateStore.claimPaths({
|
|
145
|
+
tool: options.tool,
|
|
146
|
+
sessionId: options.session,
|
|
147
|
+
instanceId: options.instance,
|
|
148
|
+
projectId: options.project,
|
|
149
|
+
projectPath: options.projectPath,
|
|
150
|
+
branch: options.branch,
|
|
151
|
+
paths: options.path || [],
|
|
152
|
+
ttlMs: Number(options.ttlMs),
|
|
153
|
+
metadata: parseJsonObject(options.metadata)
|
|
154
|
+
});
|
|
155
|
+
if (options.json) {
|
|
156
|
+
console.log(JSON.stringify(result, null, 2));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
console.log(chalk.green(`Claimed ${result.record.claimId.slice(0, 8)}`));
|
|
160
|
+
if (result.conflicts.length > 0) {
|
|
161
|
+
console.log(chalk.yellow(`Conflicts: ${result.conflicts.length}`));
|
|
162
|
+
for (const conflict of result.conflicts) {
|
|
163
|
+
console.log(
|
|
164
|
+
` ${conflict.claimId.slice(0, 8)} ${conflict.branch || ""} ${conflict.paths.join(", ")}`.trim()
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
claims.command("release").option("--claim <id>", "Claim identifier").option("--session <id>", "Session identifier").option("--instance <id>", "Instance identifier").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--branch <branch>", "Git branch").option("--reason <reason>", "Release reason").action(async (options) => {
|
|
170
|
+
const released = await canonicalStateStore.releaseClaims({
|
|
171
|
+
claimId: options.claim,
|
|
172
|
+
sessionId: options.session,
|
|
173
|
+
instanceId: options.instance,
|
|
174
|
+
projectId: options.project,
|
|
175
|
+
projectPath: options.projectPath,
|
|
176
|
+
branch: options.branch,
|
|
177
|
+
reason: options.reason
|
|
178
|
+
});
|
|
179
|
+
console.log(chalk.green(`Released ${released} claim(s)`));
|
|
180
|
+
});
|
|
181
|
+
claims.command("show").option("--project <id>", "Project identifier").option("--project-path <path>", "Project path").option("--all", "Show released and expired claims too").option("--json", "Emit JSON output").action(async (options) => {
|
|
182
|
+
const records = await canonicalStateStore.listPathClaims({
|
|
183
|
+
projectId: options.project,
|
|
184
|
+
projectPath: options.projectPath,
|
|
185
|
+
activeOnly: !options.all
|
|
186
|
+
});
|
|
187
|
+
if (options.json) {
|
|
188
|
+
console.log(JSON.stringify(records, null, 2));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (records.length === 0) {
|
|
192
|
+
console.log(chalk.yellow("No claims found"));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
for (const claim of records) {
|
|
196
|
+
const scopes = [
|
|
197
|
+
claim.branch ? `branch:${claim.branch}` : "",
|
|
198
|
+
...claim.paths.map((item) => `path:${item}`)
|
|
199
|
+
].filter(Boolean).join(", ");
|
|
200
|
+
console.log(
|
|
201
|
+
`${claim.claimId.slice(0, 8)} ${claim.status} ${claim.tool} ${scopes || "(no scope)"}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
const github = cmd.command("github").description("GitHub projection state");
|
|
206
|
+
github.command("refresh").description("Refresh current repo branch PR state from GitHub CLI").option("--json", "Emit JSON output").action(async (options) => {
|
|
207
|
+
const projection = await refreshCurrentRepoPullRequestState();
|
|
208
|
+
if (!projection) {
|
|
209
|
+
console.log(
|
|
210
|
+
chalk.yellow(
|
|
211
|
+
"No GitHub PR projection available for current repo/branch"
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (options.json) {
|
|
217
|
+
console.log(JSON.stringify(projection, null, 2));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
console.log(chalk.green(`Refreshed PR #${projection.prNumber}`));
|
|
221
|
+
console.log(`${projection.state} ${projection.title}`);
|
|
222
|
+
console.log(projection.url);
|
|
223
|
+
});
|
|
224
|
+
github.command("show").description("Show cached current repo branch PR projection").option("--json", "Emit JSON output").action(async (options) => {
|
|
225
|
+
const info = getCurrentRepoGitHubInfo();
|
|
226
|
+
if (!info) {
|
|
227
|
+
console.log(chalk.yellow("Not in a GitHub repository"));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const projection = await canonicalStateStore.getGitHubPullRequest({
|
|
231
|
+
repo: info.repo,
|
|
232
|
+
branch: info.branch
|
|
233
|
+
});
|
|
234
|
+
if (!projection) {
|
|
235
|
+
console.log(
|
|
236
|
+
chalk.yellow("No cached GitHub PR projection for current branch")
|
|
237
|
+
);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (options.json) {
|
|
241
|
+
console.log(JSON.stringify(projection, null, 2));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
console.log(chalk.bold(`PR #${projection.prNumber}`));
|
|
245
|
+
console.log(`${projection.state} ${projection.title}`);
|
|
246
|
+
console.log(`Repo: ${projection.repo}`);
|
|
247
|
+
console.log(
|
|
248
|
+
`Branch: ${projection.headRefName} -> ${projection.baseRefName}`
|
|
249
|
+
);
|
|
250
|
+
if (projection.reviewDecision) {
|
|
251
|
+
console.log(`Review: ${projection.reviewDecision}`);
|
|
252
|
+
}
|
|
253
|
+
if (projection.statusCheckRollup) {
|
|
254
|
+
console.log(`Checks: ${projection.statusCheckRollup}`);
|
|
255
|
+
}
|
|
256
|
+
console.log(`Synced: ${new Date(projection.lastSyncedAt).toISOString()}`);
|
|
257
|
+
console.log(projection.url);
|
|
258
|
+
});
|
|
259
|
+
return cmd;
|
|
260
|
+
}
|
|
261
|
+
var state_default = createStateCommand;
|
|
262
|
+
export {
|
|
263
|
+
createStateCommand,
|
|
264
|
+
state_default as default
|
|
265
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
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 { homedir, hostname } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { existsSync, readFileSync } from "fs";
|
|
10
|
+
import { createHash } from "crypto";
|
|
11
|
+
import Database from "better-sqlite3";
|
|
12
|
+
import { CloudSyncEngine } from "../../core/storage/cloud-sync.js";
|
|
13
|
+
const DEFAULT_ENDPOINT = "https://provenant-api.jpwu03.workers.dev";
|
|
14
|
+
function loadSyncConfig(projectDir) {
|
|
15
|
+
const envKey = process.env["PROVENANT_API_KEY"];
|
|
16
|
+
const envProject = process.env["PROVENANT_PROJECT_ID"];
|
|
17
|
+
if (envKey) {
|
|
18
|
+
return buildConfig(
|
|
19
|
+
envKey,
|
|
20
|
+
envProject || createHash("sha256").update(projectDir).digest("hex").slice(0, 16),
|
|
21
|
+
process.env["PROVENANT_API_URL"] || DEFAULT_ENDPOINT,
|
|
22
|
+
projectDir
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const cfgPath = join(homedir(), ".stackmemory", "config.json");
|
|
26
|
+
if (!existsSync(cfgPath)) return null;
|
|
27
|
+
try {
|
|
28
|
+
const cfg = JSON.parse(readFileSync(cfgPath, "utf8"));
|
|
29
|
+
if (!cfg.auth?.apiKey) return null;
|
|
30
|
+
return buildConfig(
|
|
31
|
+
cfg.auth.apiKey,
|
|
32
|
+
cfg.auth.projectId || createHash("sha256").update(projectDir).digest("hex").slice(0, 16),
|
|
33
|
+
cfg.auth.apiUrl || DEFAULT_ENDPOINT,
|
|
34
|
+
projectDir
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function buildConfig(apiKey, projectId, endpoint, projectDir) {
|
|
41
|
+
return {
|
|
42
|
+
enabled: true,
|
|
43
|
+
endpoint,
|
|
44
|
+
apiKey,
|
|
45
|
+
projectId,
|
|
46
|
+
clientId: createHash("sha256").update(hostname() + projectDir).digest("hex").slice(0, 16),
|
|
47
|
+
batchSize: 100,
|
|
48
|
+
conflictResolution: "newest_wins",
|
|
49
|
+
generationalPolicy: {
|
|
50
|
+
youngMaxAgeDays: 1,
|
|
51
|
+
matureMaxAgeDays: 7
|
|
52
|
+
},
|
|
53
|
+
timeoutMs: 3e4,
|
|
54
|
+
retryAttempts: 3,
|
|
55
|
+
retryBaseDelayMs: 1e3
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function getDbPath(projectDir) {
|
|
59
|
+
const contextDb = join(projectDir, ".stackmemory", "context.db");
|
|
60
|
+
if (existsSync(contextDb)) return contextDb;
|
|
61
|
+
const localDb = join(projectDir, ".stackmemory", "stackmemory.db");
|
|
62
|
+
if (existsSync(localDb)) return localDb;
|
|
63
|
+
const globalDb = join(homedir(), ".stackmemory", "stackmemory.db");
|
|
64
|
+
if (existsSync(globalDb)) return globalDb;
|
|
65
|
+
return contextDb;
|
|
66
|
+
}
|
|
67
|
+
function createLoginCommand() {
|
|
68
|
+
const cmd = new Command("login").description("Connect to Provenant cloud sync").argument("<email>", "Your email address").option("--workspace <name>", "Workspace name (for new accounts)").option("--reset", "Reset API key (revokes existing keys)").action(
|
|
69
|
+
async (email, options) => {
|
|
70
|
+
const endpoint = process.env["PROVENANT_API_URL"] || DEFAULT_ENDPOINT;
|
|
71
|
+
try {
|
|
72
|
+
const path = options.reset ? "/v1/setup/reset" : "/v1/setup";
|
|
73
|
+
const body = { email };
|
|
74
|
+
if (!options.reset) {
|
|
75
|
+
body["workspaceName"] = options.workspace ?? email.split("@")[0];
|
|
76
|
+
}
|
|
77
|
+
const res = await fetch(`${endpoint}${path}`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify(body)
|
|
81
|
+
});
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
if (res.status === 409 && !options.reset) {
|
|
84
|
+
console.log(
|
|
85
|
+
chalk.yellow("Workspace already exists for this email.")
|
|
86
|
+
);
|
|
87
|
+
console.log(chalk.dim(" Run: stackmemory login <email> --reset"));
|
|
88
|
+
console.log(chalk.dim(` Workspace ID: ${data.workspaceId}`));
|
|
89
|
+
console.log(chalk.dim(` Project ID: ${data.projectId}`));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!res.ok || !data.apiKey) {
|
|
93
|
+
console.error(
|
|
94
|
+
chalk.red(`Login failed: ${data.error || res.statusText}`)
|
|
95
|
+
);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const smDir = join(homedir(), ".stackmemory");
|
|
99
|
+
const cfgPath = join(smDir, "config.json");
|
|
100
|
+
let cfg = {};
|
|
101
|
+
if (existsSync(cfgPath)) {
|
|
102
|
+
try {
|
|
103
|
+
cfg = JSON.parse(readFileSync(cfgPath, "utf8"));
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
cfg["auth"] = {
|
|
108
|
+
apiKey: data.apiKey,
|
|
109
|
+
apiUrl: endpoint,
|
|
110
|
+
projectId: data.projectId,
|
|
111
|
+
workspaceId: data.workspaceId,
|
|
112
|
+
email
|
|
113
|
+
};
|
|
114
|
+
const { writeFileSync, mkdirSync } = await import("fs");
|
|
115
|
+
mkdirSync(smDir, { recursive: true });
|
|
116
|
+
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
117
|
+
console.log(chalk.green("Logged in to Provenant cloud sync."));
|
|
118
|
+
console.log(chalk.dim(` Workspace: ${data.workspaceId}`));
|
|
119
|
+
console.log(chalk.dim(` Project: ${data.projectId}`));
|
|
120
|
+
console.log(chalk.dim(` Config: ${cfgPath}`));
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(
|
|
123
|
+
chalk.red(
|
|
124
|
+
`Login failed: ${err instanceof Error ? err.message : err}`
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
return cmd;
|
|
132
|
+
}
|
|
133
|
+
function createSyncCommand() {
|
|
134
|
+
const cmd = new Command("sync").description(
|
|
135
|
+
"Provenant cloud sync operations"
|
|
136
|
+
);
|
|
137
|
+
cmd.command("push").description("Push local changes to Provenant cloud").option("--force", "Push all data, ignoring cursors").option("--json", "Output as JSON").action(async (options) => {
|
|
138
|
+
const projectDir = process.cwd();
|
|
139
|
+
const config = loadSyncConfig(projectDir);
|
|
140
|
+
if (!config) {
|
|
141
|
+
console.error(
|
|
142
|
+
chalk.yellow(
|
|
143
|
+
"Cloud sync not configured. Run `stackmemory login` to connect."
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
const dbPath = getDbPath(projectDir);
|
|
149
|
+
if (!existsSync(dbPath)) {
|
|
150
|
+
console.error(chalk.red("No StackMemory database found."));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const db = new Database(dbPath);
|
|
154
|
+
const engine = new CloudSyncEngine(db, config);
|
|
155
|
+
try {
|
|
156
|
+
const result = await engine.push();
|
|
157
|
+
if (options.json) {
|
|
158
|
+
console.log(JSON.stringify(result, null, 2));
|
|
159
|
+
} else if (result.success) {
|
|
160
|
+
console.log(
|
|
161
|
+
chalk.green(`Pushed ${result.pushed} entities.`) + (result.conflicts > 0 ? chalk.yellow(` ${result.conflicts} conflicts.`) : "")
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
console.error(chalk.red(`Push failed: ${result.error}`));
|
|
165
|
+
}
|
|
166
|
+
} finally {
|
|
167
|
+
db.close();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
cmd.command("pull").description("Pull changes from Provenant cloud").option("--tables <tables...>", "Only pull specific tables").option("--json", "Output as JSON").action(async (options) => {
|
|
171
|
+
const projectDir = process.cwd();
|
|
172
|
+
const config = loadSyncConfig(projectDir);
|
|
173
|
+
if (!config) {
|
|
174
|
+
console.error(
|
|
175
|
+
chalk.yellow(
|
|
176
|
+
"Cloud sync not configured. Run `stackmemory login` to connect."
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
const dbPath = getDbPath(projectDir);
|
|
182
|
+
if (!existsSync(dbPath)) {
|
|
183
|
+
console.error(chalk.red("No StackMemory database found."));
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const db = new Database(dbPath);
|
|
187
|
+
const engine = new CloudSyncEngine(db, config);
|
|
188
|
+
try {
|
|
189
|
+
const result = await engine.pull(options.tables);
|
|
190
|
+
if (options.json) {
|
|
191
|
+
console.log(JSON.stringify(result, null, 2));
|
|
192
|
+
} else if (result.success) {
|
|
193
|
+
console.log(
|
|
194
|
+
chalk.green(
|
|
195
|
+
`Pulled ${result.pulled} entities, applied ${result.applied}.`
|
|
196
|
+
) + (result.conflicts > 0 ? chalk.yellow(` ${result.conflicts} conflicts.`) : "")
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
console.error(chalk.red(`Pull failed: ${result.error}`));
|
|
200
|
+
}
|
|
201
|
+
} finally {
|
|
202
|
+
db.close();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
cmd.command("status").description("Show cloud sync status").option("--json", "Output as JSON").action(async (options) => {
|
|
206
|
+
const projectDir = process.cwd();
|
|
207
|
+
const config = loadSyncConfig(projectDir);
|
|
208
|
+
if (!config) {
|
|
209
|
+
if (options.json) {
|
|
210
|
+
console.log(
|
|
211
|
+
JSON.stringify({ connected: false, message: "Not configured" })
|
|
212
|
+
);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(
|
|
215
|
+
chalk.dim("Cloud sync not configured. Run `stackmemory login`.")
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const dbPath = getDbPath(projectDir);
|
|
221
|
+
if (!existsSync(dbPath)) {
|
|
222
|
+
console.log(chalk.dim("No StackMemory database found."));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const db = new Database(dbPath);
|
|
226
|
+
const engine = new CloudSyncEngine(db, config);
|
|
227
|
+
try {
|
|
228
|
+
const status = engine.status();
|
|
229
|
+
if (options.json) {
|
|
230
|
+
console.log(JSON.stringify(status, null, 2));
|
|
231
|
+
} else {
|
|
232
|
+
console.log(chalk.bold("Cloud Sync Status"));
|
|
233
|
+
console.log(
|
|
234
|
+
` Connected: ${status.connected ? chalk.green("yes") : chalk.red("no")}`
|
|
235
|
+
);
|
|
236
|
+
console.log(` Endpoint: ${status.endpoint ?? "none"}`);
|
|
237
|
+
console.log(` Last push: ${status.lastPushAt ?? "never"}`);
|
|
238
|
+
console.log(` Last pull: ${status.lastPullAt ?? "never"}`);
|
|
239
|
+
console.log(` Pending: ${status.pendingPushCount} items`);
|
|
240
|
+
console.log(
|
|
241
|
+
` Conflicts: ${status.conflictCount > 0 ? chalk.yellow(String(status.conflictCount)) : "0"}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
db.close();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return cmd;
|
|
249
|
+
}
|
|
250
|
+
export {
|
|
251
|
+
createLoginCommand,
|
|
252
|
+
createSyncCommand
|
|
253
|
+
};
|
|
@@ -5,10 +5,20 @@ const __dirname = __pathDirname(__filename);
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import Database from "better-sqlite3";
|
|
7
7
|
import { join } from "path";
|
|
8
|
-
import { existsSync } from "fs";
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
9
9
|
import {
|
|
10
10
|
LinearTaskManager
|
|
11
11
|
} from "../../features/tasks/linear-task-manager.js";
|
|
12
|
+
import {
|
|
13
|
+
parseMasterTasks,
|
|
14
|
+
getNextTask,
|
|
15
|
+
addTaskToFile,
|
|
16
|
+
updateTaskInFile
|
|
17
|
+
} from "../../core/tasks/md-task-parser.js";
|
|
18
|
+
import {
|
|
19
|
+
MASTER_TASKS_TEMPLATE,
|
|
20
|
+
TASKS_CONFIG_TEMPLATE
|
|
21
|
+
} from "../../core/tasks/master-tasks-template.js";
|
|
12
22
|
function getTaskStore(projectRoot) {
|
|
13
23
|
const dbPath = join(projectRoot, ".stackmemory", "context.db");
|
|
14
24
|
if (!existsSync(dbPath)) {
|
|
@@ -191,8 +201,127 @@ Tags: ${tags.join(", ")}`);
|
|
|
191
201
|
console.error("\u274C Failed to show task:", error.message);
|
|
192
202
|
}
|
|
193
203
|
});
|
|
204
|
+
tasks.command("init").description("Scaffold .stackmemory/tasks/master-tasks.md").action(() => {
|
|
205
|
+
const projectRoot = process.cwd();
|
|
206
|
+
const tasksDir = join(projectRoot, ".stackmemory", "tasks");
|
|
207
|
+
const mdPath = join(tasksDir, "master-tasks.md");
|
|
208
|
+
const configPath = join(tasksDir, "config.json");
|
|
209
|
+
if (existsSync(mdPath)) {
|
|
210
|
+
console.log(`Already exists: ${mdPath}`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
214
|
+
writeFileSync(mdPath, MASTER_TASKS_TEMPLATE, "utf-8");
|
|
215
|
+
writeFileSync(
|
|
216
|
+
configPath,
|
|
217
|
+
JSON.stringify(TASKS_CONFIG_TEMPLATE, null, 2),
|
|
218
|
+
"utf-8"
|
|
219
|
+
);
|
|
220
|
+
console.log(`Created: ${mdPath}`);
|
|
221
|
+
console.log(`Created: ${configPath}`);
|
|
222
|
+
});
|
|
223
|
+
const md = new Command("md").description(
|
|
224
|
+
"Local-first task management via master-tasks.md"
|
|
225
|
+
);
|
|
226
|
+
md.command("list").alias("ls").description("List tasks from master-tasks.md").option("-p, --priority <P>", "Filter by priority (P0, P1, P2, P3)").option(
|
|
227
|
+
"-s, --status <status>",
|
|
228
|
+
"Filter by status (todo, active, done, blocked, cut)"
|
|
229
|
+
).option("-o, --owner <owner>", "Filter by owner (@me, @agent, @defer)").option("--json", "Output as JSON").action((options) => {
|
|
230
|
+
const mdPath = resolveMdPath();
|
|
231
|
+
if (!mdPath) return;
|
|
232
|
+
let tasks2 = parseMasterTasks(readFileSync(mdPath, "utf-8"));
|
|
233
|
+
if (options.priority)
|
|
234
|
+
tasks2 = tasks2.filter((t) => t.priority === options.priority);
|
|
235
|
+
if (options.status)
|
|
236
|
+
tasks2 = tasks2.filter((t) => t.status === options.status);
|
|
237
|
+
if (options.owner) tasks2 = tasks2.filter((t) => t.owner === options.owner);
|
|
238
|
+
if (options.json) {
|
|
239
|
+
console.log(JSON.stringify(tasks2, null, 2));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (tasks2.length === 0) {
|
|
243
|
+
console.log("No tasks found");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log(`
|
|
247
|
+
Tasks (${tasks2.length})
|
|
248
|
+
`);
|
|
249
|
+
for (const t of tasks2) {
|
|
250
|
+
const pColor = t.priority === "P0" ? "\x1B[31m" : t.priority === "P1" ? "\x1B[33m" : "\x1B[90m";
|
|
251
|
+
const sIcon = t.status === "done" ? "[x]" : t.status === "active" ? "[>]" : t.status === "blocked" ? "[!]" : "[ ]";
|
|
252
|
+
console.log(
|
|
253
|
+
`${sIcon} ${pColor}${t.priority}\x1B[0m ${t.id} ${t.task} ${t.owner} ${t.branchPr ? `(${t.branchPr})` : ""}`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
console.log("");
|
|
257
|
+
});
|
|
258
|
+
md.command("next").description("Show the next task to work on").option("--json", "Output as JSON").action((options) => {
|
|
259
|
+
const mdPath = resolveMdPath();
|
|
260
|
+
if (!mdPath) return;
|
|
261
|
+
const tasks2 = parseMasterTasks(readFileSync(mdPath, "utf-8"));
|
|
262
|
+
const next = getNextTask(tasks2);
|
|
263
|
+
if (!next) {
|
|
264
|
+
console.log("No actionable tasks");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (options.json) {
|
|
268
|
+
console.log(JSON.stringify(next));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
console.log(`
|
|
272
|
+
Next: ${next.id} [${next.priority}] ${next.task}`);
|
|
273
|
+
console.log(` Owner: ${next.owner} | Sync: ${next.sync}`);
|
|
274
|
+
if (next.notes) console.log(` Notes: ${next.notes}`);
|
|
275
|
+
console.log("");
|
|
276
|
+
});
|
|
277
|
+
md.command("add <description>").description("Add a task to master-tasks.md").option("-p, --priority <P>", "Priority (P0-P3)", "P1").option("-o, --owner <owner>", "Owner (@me, @agent, @defer)", "@me").option("-s, --sync <sync>", "Sync target (local, linear, gh)", "local").option("-b, --branch <branch>", "Branch or PR").option("-n, --notes <notes>", "Notes").action((description, options) => {
|
|
278
|
+
const mdPath = resolveMdPath();
|
|
279
|
+
if (!mdPath) return;
|
|
280
|
+
const id = addTaskToFile(mdPath, {
|
|
281
|
+
priority: options.priority,
|
|
282
|
+
status: "todo",
|
|
283
|
+
owner: options.owner,
|
|
284
|
+
sync: options.sync,
|
|
285
|
+
task: description,
|
|
286
|
+
branchPr: options.branch || "",
|
|
287
|
+
notes: options.notes || ""
|
|
288
|
+
});
|
|
289
|
+
console.log(`Added: ${id} ${description}`);
|
|
290
|
+
});
|
|
291
|
+
md.command("update <taskId>").description("Update a task in master-tasks.md").option(
|
|
292
|
+
"-s, --status <status>",
|
|
293
|
+
"New status (todo, active, done, blocked, cut)"
|
|
294
|
+
).option("-p, --priority <P>", "New priority (P0-P3)").option("-o, --owner <owner>", "New owner").option("-b, --branch <branch>", "Branch or PR").option("-n, --notes <notes>", "Notes").option("--sync <sync>", "Sync target (local, linear, gh)").action((taskId, options) => {
|
|
295
|
+
const mdPath = resolveMdPath();
|
|
296
|
+
if (!mdPath) return;
|
|
297
|
+
try {
|
|
298
|
+
const updates = {};
|
|
299
|
+
if (options.status) updates.status = options.status;
|
|
300
|
+
if (options.priority) updates.priority = options.priority;
|
|
301
|
+
if (options.owner) updates.owner = options.owner;
|
|
302
|
+
if (options.branch) updates.branchPr = options.branch;
|
|
303
|
+
if (options.notes) updates.notes = options.notes;
|
|
304
|
+
if (options.sync) updates.sync = options.sync;
|
|
305
|
+
updateTaskInFile(mdPath, taskId.toUpperCase(), updates);
|
|
306
|
+
console.log(`Updated: ${taskId.toUpperCase()}`);
|
|
307
|
+
} catch (err) {
|
|
308
|
+
console.error(`Error: ${err.message}`);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
tasks.addCommand(md);
|
|
194
312
|
return tasks;
|
|
195
313
|
}
|
|
314
|
+
function resolveMdPath() {
|
|
315
|
+
const projectRoot = process.cwd();
|
|
316
|
+
const smPath = join(projectRoot, ".stackmemory", "tasks", "master-tasks.md");
|
|
317
|
+
if (existsSync(smPath)) return smPath;
|
|
318
|
+
const rootPath = join(projectRoot, "master-tasks.md");
|
|
319
|
+
if (existsSync(rootPath)) return rootPath;
|
|
320
|
+
console.error(
|
|
321
|
+
'No master-tasks.md found. Run "stackmemory tasks init" first.'
|
|
322
|
+
);
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
196
325
|
function findTaskByPartialId(projectRoot, partialId) {
|
|
197
326
|
const dbPath = join(projectRoot, ".stackmemory", "context.db");
|
|
198
327
|
if (!existsSync(dbPath)) return null;
|