@stackmemoryai/stackmemory 1.12.0 → 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 +3 -1
- 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/codex-sm.js +51 -11
- package/dist/src/cli/commands/brain.js +206 -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/operator.js +127 -0
- package/dist/src/cli/commands/orchestrate.js +2 -0
- package/dist/src/cli/commands/orchestrator.js +3 -2
- 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/setup.js +1 -4
- 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/hermes-sm.js +224 -0
- package/dist/src/cli/index.js +15 -10
- package/dist/src/cli/utils/real-cli-bin.js +72 -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/token-estimator.js +24 -1
- package/dist/src/core/config/feature-flags.js +2 -6
- package/dist/src/core/context/frame-database.js +44 -0
- package/dist/src/core/context/recursive-context-manager.js +1 -1
- package/dist/src/core/context/rehydration.js +2 -1
- package/dist/src/core/database/sqlite-adapter.js +14 -1
- package/dist/src/core/models/model-router.js +33 -1
- package/dist/src/core/models/provider-pricing.js +58 -4
- 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/retrieval/llm-context-retrieval.js +5 -4
- package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
- package/dist/src/core/skill-packs/types.js +14 -1
- 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/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 +45 -0
- package/dist/src/daemon/services/desire-path-service.js +566 -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 +28 -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/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/{integrations/diffmem/index.js → features/web/client/hooks/use-socket.js} +6 -5
- package/dist/src/features/web/client/lib/utils.js +12 -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 +4 -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 +218 -11
- package/dist/src/integrations/claude-code/task-coordinator.js +2 -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 +27 -52
- package/dist/src/integrations/mcp/server.js +122 -335
- package/dist/src/integrations/mcp/tool-alias-registry.js +0 -73
- package/dist/src/integrations/mcp/tool-definitions.js +111 -510
- package/dist/src/mcp/stackmemory-mcp-server.js +404 -379
- package/dist/src/orchestrators/multimodal/determinism.js +2 -1
- package/dist/src/orchestrators/multimodal/harness.js +2 -1
- package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
- 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 +18 -9
- 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/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/cross-search-handlers.js +0 -188
- 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/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 -354
- package/dist/src/integrations/ralph/index.js +0 -17
- package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -416
- 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 -1005
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
- package/scripts/ralph-loop-implementation.js +0 -404
- /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
- /package/dist/src/{integrations/greptile → features/operator}/types.js +0 -0
- /package/dist/src/{integrations/ralph/types.js → features/web/client/next-env.d.js} +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
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 { networkInterfaces } from "os";
|
|
8
|
+
import {
|
|
9
|
+
PortalServer,
|
|
10
|
+
ensureToken,
|
|
11
|
+
readStatus,
|
|
12
|
+
stopRunning
|
|
13
|
+
} from "../../features/portal/index.js";
|
|
14
|
+
import { DEFAULT_PORTAL_CONFIG } from "../../features/portal/types.js";
|
|
15
|
+
function tailscaleIp() {
|
|
16
|
+
const nets = networkInterfaces();
|
|
17
|
+
for (const addrs of Object.values(nets)) {
|
|
18
|
+
for (const a of addrs ?? []) {
|
|
19
|
+
if (a.family === "IPv4" && a.address.startsWith("100.")) {
|
|
20
|
+
return a.address;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
function printAccessUrls(host, port, token) {
|
|
27
|
+
const path = token ? `/?token=${token}` : "/";
|
|
28
|
+
const ts = tailscaleIp();
|
|
29
|
+
console.log(chalk.cyan("\nAccess your agent:"));
|
|
30
|
+
if (ts) {
|
|
31
|
+
console.log(
|
|
32
|
+
chalk.green(` http://${ts}:${port}${path}`) + chalk.gray(" (tailnet)")
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
console.log(chalk.gray(` http://localhost:${port}${path}`));
|
|
36
|
+
if (!ts && host === "0.0.0.0") {
|
|
37
|
+
console.log(
|
|
38
|
+
chalk.gray(
|
|
39
|
+
" Tip: connect this machine to Tailscale, then use its 100.x address."
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function createPortalCommand() {
|
|
45
|
+
const cmd = new Command("portal").description("Browser terminal into a persistent tmux Claude Code session").addHelpText(
|
|
46
|
+
"after",
|
|
47
|
+
`
|
|
48
|
+
Examples:
|
|
49
|
+
stackmemory portal start Start the portal (foreground)
|
|
50
|
+
stackmemory portal start --port 8080 Use a custom port
|
|
51
|
+
stackmemory portal start --command "claude --resume"
|
|
52
|
+
stackmemory portal start --no-auth Disable token (Tailscale-only)
|
|
53
|
+
stackmemory portal status Show running status + access URL
|
|
54
|
+
stackmemory portal stop Stop a backgrounded portal
|
|
55
|
+
stackmemory portal token Print the access token
|
|
56
|
+
|
|
57
|
+
Run it on a VPS behind Tailscale for 24/7 agents. See docs/guides/PORTAL.md.
|
|
58
|
+
`
|
|
59
|
+
);
|
|
60
|
+
cmd.command("start").description("Start the portal server (runs in the foreground)").option(
|
|
61
|
+
"--port <number>",
|
|
62
|
+
"Port to listen on",
|
|
63
|
+
String(DEFAULT_PORTAL_CONFIG.port)
|
|
64
|
+
).option("--host <host>", "Interface to bind", DEFAULT_PORTAL_CONFIG.host).option(
|
|
65
|
+
"--session <name>",
|
|
66
|
+
"tmux session name",
|
|
67
|
+
DEFAULT_PORTAL_CONFIG.session
|
|
68
|
+
).option(
|
|
69
|
+
"--command <cmd>",
|
|
70
|
+
"Command tmux runs in the session",
|
|
71
|
+
DEFAULT_PORTAL_CONFIG.command
|
|
72
|
+
).option("--cwd <dir>", "Working directory for the session").option("--no-auth", "Disable token auth (rely on Tailscale)").action(async (options) => {
|
|
73
|
+
const server = new PortalServer({
|
|
74
|
+
port: parseInt(options.port, 10),
|
|
75
|
+
host: options.host,
|
|
76
|
+
session: options.session,
|
|
77
|
+
command: options.command,
|
|
78
|
+
cwd: options.cwd,
|
|
79
|
+
noAuth: options.auth === false
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
const status = await server.start();
|
|
83
|
+
const cfg = server.getConfig();
|
|
84
|
+
console.log(chalk.green("\u2713 StackMemory Portal started"));
|
|
85
|
+
console.log(
|
|
86
|
+
chalk.gray(
|
|
87
|
+
` Session: ${cfg.session} (tmux new-session -A -s ${cfg.session})`
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
console.log(chalk.gray(` Command: ${cfg.command}`));
|
|
91
|
+
console.log(chalk.gray(` Listening: ${cfg.host}:${status.port}`));
|
|
92
|
+
if (cfg.noAuth) {
|
|
93
|
+
console.log(
|
|
94
|
+
chalk.yellow(" Auth: disabled (anyone on the network can connect)")
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
printAccessUrls(cfg.host, cfg.port, cfg.token);
|
|
98
|
+
console.log(
|
|
99
|
+
chalk.gray(
|
|
100
|
+
"\nPress Ctrl+C to stop the portal (your tmux session keeps running)."
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(
|
|
105
|
+
chalk.red("Failed to start portal:"),
|
|
106
|
+
error.message
|
|
107
|
+
);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
const shutdown = async () => {
|
|
111
|
+
console.log(chalk.gray("\nShutting down portal\u2026"));
|
|
112
|
+
await server.stop();
|
|
113
|
+
process.exit(0);
|
|
114
|
+
};
|
|
115
|
+
process.on("SIGINT", shutdown);
|
|
116
|
+
process.on("SIGTERM", shutdown);
|
|
117
|
+
});
|
|
118
|
+
cmd.command("status").description("Show portal status and access URL").action(() => {
|
|
119
|
+
const status = readStatus();
|
|
120
|
+
if (!status.running) {
|
|
121
|
+
console.log(chalk.yellow("Portal is not running"));
|
|
122
|
+
console.log(chalk.gray(" Start with: stackmemory portal start"));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
console.log(chalk.green("Portal is running"));
|
|
126
|
+
console.log(chalk.gray(` PID: ${status.pid}`));
|
|
127
|
+
console.log(chalk.gray(` Session: ${status.session}`));
|
|
128
|
+
console.log(chalk.gray(` Listening: ${status.host}:${status.port}`));
|
|
129
|
+
if (status.startedAt) {
|
|
130
|
+
const up = Math.floor((Date.now() - status.startedAt) / 1e3);
|
|
131
|
+
console.log(chalk.gray(` Uptime: ${formatUptime(up)}`));
|
|
132
|
+
}
|
|
133
|
+
const token = ensureToken();
|
|
134
|
+
if (status.port)
|
|
135
|
+
printAccessUrls(status.host ?? "0.0.0.0", status.port, token);
|
|
136
|
+
});
|
|
137
|
+
cmd.command("stop").description("Stop a running portal server").action(() => {
|
|
138
|
+
if (stopRunning()) {
|
|
139
|
+
console.log(chalk.green("\u2713 Portal stopped"));
|
|
140
|
+
} else {
|
|
141
|
+
console.log(chalk.yellow("Portal is not running"));
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
cmd.command("token").description("Print the portal access token (auto-generated on first use)").action(() => {
|
|
145
|
+
console.log(ensureToken());
|
|
146
|
+
});
|
|
147
|
+
return cmd;
|
|
148
|
+
}
|
|
149
|
+
function formatUptime(seconds) {
|
|
150
|
+
if (seconds < 60) return `${seconds}s`;
|
|
151
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
152
|
+
if (seconds < 86400) {
|
|
153
|
+
const h = Math.floor(seconds / 3600);
|
|
154
|
+
return `${h}h ${Math.floor(seconds % 3600 / 60)}m`;
|
|
155
|
+
}
|
|
156
|
+
const d = Math.floor(seconds / 86400);
|
|
157
|
+
return `${d}d ${Math.floor(seconds % 86400 / 3600)}h`;
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
createPortalCommand
|
|
161
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
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 * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
const DIRS = ["company", "wiki", "skills", "clients", "raw", ".stackmemory"];
|
|
11
|
+
const TEMPLATES = {
|
|
12
|
+
"company/voice.md": "---\nname: Voice Guide\ndescription: How we write and communicate\n---\n\n# Voice Guide\n\n## Tone\n- [Your tone descriptors here]\n\n## Words we use\n- [Preferred terms]\n\n## Words we avoid\n- [Banned terms]\n",
|
|
13
|
+
"company/team.md": "---\nname: Team Directory\ndescription: Who works here and what they do\n---\n\n# Team\n\n| Name | Role | Contact |\n|------|------|---------||\n",
|
|
14
|
+
"company/design.md": "---\nname: Design System\ndescription: Logos, colors, components\n---\n\n# Design System\n\n## Colors\n- Primary:\n- Secondary:\n\n## Logos\n- [paths or URLs]\n",
|
|
15
|
+
"wiki/README.md": "# Wiki \u2014 SOPs & Playbooks\n\nAdd markdown files here. Files with skill frontmatter become Claude skills.\n\nSOPs should follow the Company OS schema:\n\n- `## Objective`\n- `## Procedure`\n- `## Verification`\n- `## Non-compliance`\n\nEach SOP must reference a PROSE Expectation from `docs/specs/COMPANY-OS-PROSE.md`.\n",
|
|
16
|
+
"wiki/SOP-301-onboarding.md": "# SOP-301 New Hire Onboarding\n\n**Owner:** People Ops \n**Status:** Active \n**Related PROSE Expectation:** [E.1 Onboarding completeness](../docs/specs/COMPANY-OS-PROSE.md#e1-onboarding-completeness)\n\n## Objective\nEnsure every new hire has accounts, hardware, and access documented before their start date.\n\n## Procedure\n\n1. **Pre-start checklist (5 days before)**\n - Hiring manager opens an onboarding ticket.\n - People Ops confirms laptop requirement and shipping address.\n\n2. **Account provisioning (3 days before)**\n - IT creates SSO account and adds the hire to default groups.\n - People Ops sends welcome email with first-week schedule.\n\n3. **Access verification (1 day before)**\n - Hiring manager verifies the hire can log in to primary systems.\n\n## Verification\n\n- Run audit: `stackmemory company-os audit onboarding`\n- Expected result: 100% of hires in last 30 days have completed checklist.\n\n## Non-compliance\n\nOnboarding missing SSO access or hardware assignment on start date is non-compliant.\n",
|
|
17
|
+
"skills/README.md": '# Skills\n\nClaude skill-packs. Each file is a markdown instruction set with frontmatter.\n\n```yaml\n---\nname: skill-name\ndescription: What this skill does\nactivates_on: [keyword1, keyword2]\nversion: "1.0"\n---\n```\n',
|
|
18
|
+
"clients/README.md": "# Clients\n\nEach client gets a subfolder with icp.md, voice.md, campaigns/, context/.\n",
|
|
19
|
+
"raw/README.md": "# Raw\n\nUnstructured data: transcripts, research, scrapes.\n",
|
|
20
|
+
".stackmemory/config.yml": `# StackMemory Company OS Configuration
|
|
21
|
+
|
|
22
|
+
sources:
|
|
23
|
+
- path: ./company
|
|
24
|
+
type: reference
|
|
25
|
+
- path: ./wiki
|
|
26
|
+
type: sop
|
|
27
|
+
- path: ./skills
|
|
28
|
+
type: skill
|
|
29
|
+
- path: ./raw
|
|
30
|
+
type: raw
|
|
31
|
+
|
|
32
|
+
tenants: {}
|
|
33
|
+
|
|
34
|
+
freshness_threshold_hours: 24
|
|
35
|
+
|
|
36
|
+
skill_rot:
|
|
37
|
+
enabled: true
|
|
38
|
+
stale_days: 90
|
|
39
|
+
correction_threshold: 5
|
|
40
|
+
`
|
|
41
|
+
};
|
|
42
|
+
function createScaffoldCommand() {
|
|
43
|
+
const cmd = new Command("scaffold").alias("os-init").description(
|
|
44
|
+
"Scaffold a Company OS folder structure for local context management"
|
|
45
|
+
).option("--force", "Overwrite existing template files").option("--dir <path>", "Target directory (default: current directory)").action(async (options) => {
|
|
46
|
+
const targetDir = path.resolve(options.dir || process.cwd());
|
|
47
|
+
const created = [];
|
|
48
|
+
const skipped = [];
|
|
49
|
+
for (const dir of DIRS) {
|
|
50
|
+
const fullPath = path.join(targetDir, dir);
|
|
51
|
+
if (!fs.existsSync(fullPath)) {
|
|
52
|
+
fs.mkdirSync(fullPath, { recursive: true });
|
|
53
|
+
created.push(dir + "/");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const [relPath, content] of Object.entries(TEMPLATES)) {
|
|
57
|
+
const fullPath = path.join(targetDir, relPath);
|
|
58
|
+
const dir = path.dirname(fullPath);
|
|
59
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
60
|
+
if (fs.existsSync(fullPath) && !options.force) {
|
|
61
|
+
skipped.push(relPath);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
65
|
+
created.push(relPath);
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.cyan("\n Company OS scaffolded\n"));
|
|
68
|
+
if (created.length) {
|
|
69
|
+
console.log(chalk.green(` Created: ${created.length} files/dirs`));
|
|
70
|
+
for (const f of created) {
|
|
71
|
+
console.log(chalk.gray(` + ${f}`));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (skipped.length) {
|
|
75
|
+
console.log(chalk.gray(` Skipped: ${skipped.length} (already exist)`));
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(chalk.gray(" Next steps:"));
|
|
79
|
+
console.log(
|
|
80
|
+
chalk.gray(" 1. Edit company/voice.md with your tone and brand")
|
|
81
|
+
);
|
|
82
|
+
console.log(chalk.gray(" 2. Add skills to skills/ as markdown files"));
|
|
83
|
+
console.log(
|
|
84
|
+
chalk.gray(" 3. Set COMPANY_OS_ROOT=. in .env for MCP auto-indexing")
|
|
85
|
+
);
|
|
86
|
+
console.log();
|
|
87
|
+
});
|
|
88
|
+
return cmd;
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
createScaffoldCommand
|
|
92
|
+
};
|
|
@@ -678,7 +678,6 @@ function createSetupPluginsCommand() {
|
|
|
678
678
|
}
|
|
679
679
|
console.log(chalk.gray(`Source: ${sourcePluginsDir}
|
|
680
680
|
`));
|
|
681
|
-
const plugins = ["stackmemory", "ralph-wiggum"];
|
|
682
681
|
let installed = 0;
|
|
683
682
|
for (const plugin of plugins) {
|
|
684
683
|
const sourcePath = join(sourcePluginsDir, plugin);
|
|
@@ -735,9 +734,7 @@ function createSetupPluginsCommand() {
|
|
|
735
734
|
console.log(
|
|
736
735
|
chalk.white(" /sm-help ") + chalk.gray("Show all commands")
|
|
737
736
|
);
|
|
738
|
-
console.log(
|
|
739
|
-
chalk.white(" /ralph-loop ") + chalk.gray("Start Ralph iteration loop")
|
|
740
|
-
);
|
|
737
|
+
console.log(chalk.gray("Start Ralph iteration loop"));
|
|
741
738
|
} else {
|
|
742
739
|
console.log(chalk.yellow("No plugins installed"));
|
|
743
740
|
}
|
|
@@ -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;
|