tack-cli 0.1.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 +21 -0
- package/README.md +232 -0
- package/dist/App.d.ts +5 -0
- package/dist/App.js +17 -0
- package/dist/detectors/admin.d.ts +2 -0
- package/dist/detectors/admin.js +33 -0
- package/dist/detectors/auth.d.ts +2 -0
- package/dist/detectors/auth.js +86 -0
- package/dist/detectors/database.d.ts +2 -0
- package/dist/detectors/database.js +96 -0
- package/dist/detectors/duplicates.d.ts +2 -0
- package/dist/detectors/duplicates.js +23 -0
- package/dist/detectors/exports.d.ts +2 -0
- package/dist/detectors/exports.js +30 -0
- package/dist/detectors/framework.d.ts +2 -0
- package/dist/detectors/framework.js +71 -0
- package/dist/detectors/index.d.ts +12 -0
- package/dist/detectors/index.js +128 -0
- package/dist/detectors/jobs.d.ts +2 -0
- package/dist/detectors/jobs.js +62 -0
- package/dist/detectors/multiuser.d.ts +2 -0
- package/dist/detectors/multiuser.js +55 -0
- package/dist/detectors/payments.d.ts +2 -0
- package/dist/detectors/payments.js +49 -0
- package/dist/detectors/rules/auth.yaml +24 -0
- package/dist/detectors/rules/database.yaml +27 -0
- package/dist/detectors/rules/exports.yaml +28 -0
- package/dist/detectors/rules/framework.yaml +26 -0
- package/dist/detectors/rules/jobs.yaml +23 -0
- package/dist/detectors/rules/payments.yaml +22 -0
- package/dist/detectors/types.d.ts +2 -0
- package/dist/detectors/types.js +1 -0
- package/dist/detectors/yamlRunner.d.ts +31 -0
- package/dist/detectors/yamlRunner.js +128 -0
- package/dist/engine/cleanup.d.ts +12 -0
- package/dist/engine/cleanup.js +101 -0
- package/dist/engine/compaction.d.ts +5 -0
- package/dist/engine/compaction.js +44 -0
- package/dist/engine/compareSpec.d.ts +2 -0
- package/dist/engine/compareSpec.js +74 -0
- package/dist/engine/computeDrift.d.ts +6 -0
- package/dist/engine/computeDrift.js +133 -0
- package/dist/engine/contextPack.d.ts +4 -0
- package/dist/engine/contextPack.js +169 -0
- package/dist/engine/decisions.d.ts +4 -0
- package/dist/engine/decisions.js +21 -0
- package/dist/engine/diff.d.ts +46 -0
- package/dist/engine/diff.js +210 -0
- package/dist/engine/handoff.d.ts +7 -0
- package/dist/engine/handoff.js +469 -0
- package/dist/engine/status.d.ts +10 -0
- package/dist/engine/status.js +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +299 -0
- package/dist/lib/cli.d.ts +4 -0
- package/dist/lib/cli.js +8 -0
- package/dist/lib/files.d.ts +48 -0
- package/dist/lib/files.js +529 -0
- package/dist/lib/git.d.ts +9 -0
- package/dist/lib/git.js +96 -0
- package/dist/lib/logger.d.ts +3 -0
- package/dist/lib/logger.js +21 -0
- package/dist/lib/ndjson.d.ts +2 -0
- package/dist/lib/ndjson.js +45 -0
- package/dist/lib/notes.d.ts +8 -0
- package/dist/lib/notes.js +144 -0
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +14 -0
- package/dist/lib/project.d.ts +1 -0
- package/dist/lib/project.js +17 -0
- package/dist/lib/promptSafety.d.ts +1 -0
- package/dist/lib/promptSafety.js +20 -0
- package/dist/lib/signals.d.ts +279 -0
- package/dist/lib/signals.js +55 -0
- package/dist/lib/tty.d.ts +2 -0
- package/dist/lib/tty.js +10 -0
- package/dist/lib/validate.d.ts +9 -0
- package/dist/lib/validate.js +282 -0
- package/dist/lib/yaml.d.ts +4 -0
- package/dist/lib/yaml.js +26 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +259 -0
- package/dist/plain/colors.d.ts +5 -0
- package/dist/plain/colors.js +16 -0
- package/dist/plain/diff.d.ts +1 -0
- package/dist/plain/diff.js +129 -0
- package/dist/plain/handoff.d.ts +1 -0
- package/dist/plain/handoff.js +9 -0
- package/dist/plain/init.d.ts +1 -0
- package/dist/plain/init.js +44 -0
- package/dist/plain/notes.d.ts +5 -0
- package/dist/plain/notes.js +49 -0
- package/dist/plain/status.d.ts +2 -0
- package/dist/plain/status.js +13 -0
- package/dist/plain/watch.d.ts +1 -0
- package/dist/plain/watch.js +78 -0
- package/dist/ui/CleanupPlan.d.ts +5 -0
- package/dist/ui/CleanupPlan.js +8 -0
- package/dist/ui/DetectorSweep.d.ts +6 -0
- package/dist/ui/DetectorSweep.js +54 -0
- package/dist/ui/DriftAlert.d.ts +7 -0
- package/dist/ui/DriftAlert.js +105 -0
- package/dist/ui/Handoff.d.ts +1 -0
- package/dist/ui/Handoff.js +37 -0
- package/dist/ui/Init.d.ts +1 -0
- package/dist/ui/Init.js +117 -0
- package/dist/ui/Logo.d.ts +1 -0
- package/dist/ui/Logo.js +13 -0
- package/dist/ui/SpecSummary.d.ts +8 -0
- package/dist/ui/SpecSummary.js +15 -0
- package/dist/ui/Status.d.ts +1 -0
- package/dist/ui/Status.js +38 -0
- package/dist/ui/Watch.d.ts +1 -0
- package/dist/ui/Watch.js +136 -0
- package/dist/yoga.wasm +0 -0
- package/package.json +50 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ProjectStatus, Signal, Spec, SpecDiff, DriftState } from "../lib/signals.js";
|
|
2
|
+
export type StatusResult = {
|
|
3
|
+
spec: Spec;
|
|
4
|
+
diff: SpecDiff;
|
|
5
|
+
drift: DriftState;
|
|
6
|
+
status: ProjectStatus;
|
|
7
|
+
};
|
|
8
|
+
export declare function buildProjectStatus(spec: Spec, diff: SpecDiff, drift: DriftState): ProjectStatus;
|
|
9
|
+
export declare function computeStatusFromSignals(signals: Signal[]): StatusResult | null;
|
|
10
|
+
export declare function runStatusScan(): StatusResult | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { runAllDetectors } from "../detectors/index.js";
|
|
2
|
+
import { compareSpec } from "./compareSpec.js";
|
|
3
|
+
import { computeDrift } from "./computeDrift.js";
|
|
4
|
+
import { readSpec, writeAudit } from "../lib/files.js";
|
|
5
|
+
import { createAudit } from "../lib/signals.js";
|
|
6
|
+
import { log } from "../lib/logger.js";
|
|
7
|
+
export function buildProjectStatus(spec, diff, drift) {
|
|
8
|
+
const unresolved = drift.items.filter((i) => i.status === "unresolved");
|
|
9
|
+
const driftItems = unresolved.map((item) => ({
|
|
10
|
+
system: item.system ?? item.risk ?? item.type,
|
|
11
|
+
message: item.signal,
|
|
12
|
+
}));
|
|
13
|
+
return {
|
|
14
|
+
name: spec.project,
|
|
15
|
+
health: unresolved.length > 0 ? "drift" : "aligned",
|
|
16
|
+
driftCount: unresolved.length,
|
|
17
|
+
driftItems,
|
|
18
|
+
lastScan: new Date().toISOString(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function computeStatusFromSignals(signals) {
|
|
22
|
+
const startedAt = Date.now();
|
|
23
|
+
const spec = readSpec();
|
|
24
|
+
if (!spec)
|
|
25
|
+
return null;
|
|
26
|
+
const audit = createAudit(signals);
|
|
27
|
+
writeAudit(audit);
|
|
28
|
+
const diff = compareSpec(signals, spec);
|
|
29
|
+
const { state } = computeDrift(diff);
|
|
30
|
+
log({
|
|
31
|
+
event: "scan",
|
|
32
|
+
systems_detected: signals.filter((s) => s.category === "system").length,
|
|
33
|
+
drift_items: state.items.filter((item) => item.status === "unresolved").length,
|
|
34
|
+
duration_ms: Date.now() - startedAt,
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
spec,
|
|
38
|
+
diff,
|
|
39
|
+
drift: state,
|
|
40
|
+
status: buildProjectStatus(spec, diff, state),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function runStatusScan() {
|
|
44
|
+
const { signals } = runAllDetectors();
|
|
45
|
+
return computeStatusFromSignals(signals);
|
|
46
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { render } from "ink";
|
|
8
|
+
import minimist from "minimist";
|
|
9
|
+
import { App } from "./App.js";
|
|
10
|
+
import { usePlainOutput } from "./lib/tty.js";
|
|
11
|
+
import { runStatusScan } from "./engine/status.js";
|
|
12
|
+
import { printStatusPlain } from "./plain/status.js";
|
|
13
|
+
import { generateHandoff } from "./engine/handoff.js";
|
|
14
|
+
import { printHandoffPlain } from "./plain/handoff.js";
|
|
15
|
+
import { runInitPlain } from "./plain/init.js";
|
|
16
|
+
import { runWatchPlain } from "./plain/watch.js";
|
|
17
|
+
import { log, readRecentLogs } from "./lib/logger.js";
|
|
18
|
+
import { appendDecision, normalizeDecisionActor, readDecisionsMarkdown } from "./engine/decisions.js";
|
|
19
|
+
import { ensureTackIntegrity } from "./lib/files.js";
|
|
20
|
+
import { fileExists } from "./lib/files.js";
|
|
21
|
+
import { readSpecWithError, specExists } from "./lib/files.js";
|
|
22
|
+
import { printNotes, addNotePlain } from "./plain/notes.js";
|
|
23
|
+
import { compactNotes } from "./lib/notes.js";
|
|
24
|
+
import { runDiffPlain } from "./plain/diff.js";
|
|
25
|
+
const ASCII_LOGO = `
|
|
26
|
+
████████╗ █████╗ ██████╗██╗ ██╗
|
|
27
|
+
╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
|
|
28
|
+
██║ ███████║██║ █████╔╝
|
|
29
|
+
██║ ██╔══██║██║ ██╔═██╗
|
|
30
|
+
██║ ██║ ██║╚██████╗██║ ██╗
|
|
31
|
+
╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
|
|
32
|
+
`;
|
|
33
|
+
import updateNotifier from "update-notifier";
|
|
34
|
+
import { readFileSync } from "node:fs";
|
|
35
|
+
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
36
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
37
|
+
updateNotifier({ pkg }).notify();
|
|
38
|
+
const args = minimist(process.argv.slice(2));
|
|
39
|
+
if (args.version || args.v) {
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.log(pkg.version);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
const rawCommand = args._[0];
|
|
45
|
+
const command = rawCommand ?? (fileExists(".tack") ? "watch" : "init");
|
|
46
|
+
const VALID_COMMANDS = ["init", "status", "watch", "handoff", "log", "note", "diff", "mcp", "help"];
|
|
47
|
+
function isValidCommand(value) {
|
|
48
|
+
return VALID_COMMANDS.includes(value);
|
|
49
|
+
}
|
|
50
|
+
if (command === "help" || args.help || args.h) {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.log(`
|
|
53
|
+
${ASCII_LOGO}
|
|
54
|
+
tack — Architecture drift guard
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
npx tack init [--ink] Set up spec.yaml from detected architecture
|
|
58
|
+
npx tack status [--ink] Run a scan and show current state
|
|
59
|
+
npx tack watch [--plain] Persistent watcher with live drift alerts
|
|
60
|
+
npx tack handoff [--ink] Generate agent handoff artifacts
|
|
61
|
+
npx tack log View or append decisions
|
|
62
|
+
npx tack log events [N] Show last N log events (default 50)
|
|
63
|
+
npx tack note View/add agent notes
|
|
64
|
+
npx tack diff <base-branch> Compare architecture vs base branch (plain)
|
|
65
|
+
npx tack mcp Start MCP server (for Cursor / agent integrations)
|
|
66
|
+
npx tack help Show this help text
|
|
67
|
+
|
|
68
|
+
Output mode:
|
|
69
|
+
default: plain output for all commands except watch
|
|
70
|
+
--ink: force Ink UI for init/status/handoff
|
|
71
|
+
--plain or TACK_PLAIN=1: force plain output (including watch)
|
|
72
|
+
|
|
73
|
+
Files (all in .tack/):
|
|
74
|
+
spec.yaml Your declared architecture contract
|
|
75
|
+
_audit.yaml Latest detector sweep results
|
|
76
|
+
_drift.yaml Current unresolved drift items
|
|
77
|
+
_logs.ndjson Append-only event log
|
|
78
|
+
context.md, goals.md, assumptions.md, open_questions.md
|
|
79
|
+
handoffs/<ts>.md, handoffs/<ts>.json
|
|
80
|
+
`);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
if (!isValidCommand(command)) {
|
|
84
|
+
// eslint-disable-next-line no-console
|
|
85
|
+
console.error(`Unknown command: "${command}". Run "npx tack help" for usage.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const normalizedCommand = command;
|
|
89
|
+
// tack mcp: start the MCP server (stdio). Run from a project root that has .tack/
|
|
90
|
+
if (normalizedCommand === "mcp") {
|
|
91
|
+
const cwdTack = path.join(process.cwd(), ".tack");
|
|
92
|
+
if (!existsSync(cwdTack)) {
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.error("Run `tack mcp` from a project root that contains a .tack/ directory.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
98
|
+
const mcpHere = path.join(dir, "mcp.js");
|
|
99
|
+
const mcpInDist = path.join(dir, "..", "dist", "mcp.js");
|
|
100
|
+
const mcpPath = existsSync(mcpHere) ? mcpHere : mcpInDist;
|
|
101
|
+
if (!existsSync(mcpPath)) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.error("MCP server not found. Run `npm run build` in the tack repo first.");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const result = spawnSync(process.execPath, [mcpPath], { stdio: "inherit", cwd: process.cwd() });
|
|
107
|
+
process.exit(result.status ?? 1);
|
|
108
|
+
}
|
|
109
|
+
const forcePlain = usePlainOutput();
|
|
110
|
+
const forceInk = Boolean(args.ink || process.argv.includes("--ink"));
|
|
111
|
+
const shouldUseInk = normalizedCommand === "watch" ? !forcePlain : forceInk && !forcePlain;
|
|
112
|
+
function printFatal(err) {
|
|
113
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
114
|
+
// eslint-disable-next-line no-console
|
|
115
|
+
console.error(`✗ ${message}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
if (normalizedCommand === "status" || normalizedCommand === "watch" || normalizedCommand === "handoff") {
|
|
119
|
+
if (specExists()) {
|
|
120
|
+
const { error } = readSpecWithError();
|
|
121
|
+
if (error) {
|
|
122
|
+
// eslint-disable-next-line no-console
|
|
123
|
+
console.error(`⚠ ${error}`);
|
|
124
|
+
// eslint-disable-next-line no-console
|
|
125
|
+
console.error("Fix .tack/spec.yaml syntax and run again.");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let repaired = [];
|
|
130
|
+
try {
|
|
131
|
+
repaired = ensureTackIntegrity().repaired;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
printFatal(err);
|
|
135
|
+
}
|
|
136
|
+
if (repaired.length > 0) {
|
|
137
|
+
log({ event: "repair", files: repaired });
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.log(`Repaired .tack integrity: ${repaired.join(", ")}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (normalizedCommand === "log") {
|
|
143
|
+
const sub = args._[1];
|
|
144
|
+
if (!sub) {
|
|
145
|
+
// eslint-disable-next-line no-console
|
|
146
|
+
console.log(readDecisionsMarkdown());
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
if (sub === "events") {
|
|
150
|
+
const rawLimit = args._[2];
|
|
151
|
+
let limit = 50;
|
|
152
|
+
if (typeof rawLimit === "string") {
|
|
153
|
+
const parsed = Number.parseInt(rawLimit, 10);
|
|
154
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
155
|
+
limit = parsed;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const events = readRecentLogs(limit);
|
|
159
|
+
if (!events.length) {
|
|
160
|
+
// eslint-disable-next-line no-console
|
|
161
|
+
console.log("No log events recorded.");
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
for (const event of events) {
|
|
165
|
+
// eslint-disable-next-line no-console
|
|
166
|
+
console.log(JSON.stringify(event));
|
|
167
|
+
}
|
|
168
|
+
process.exit(0);
|
|
169
|
+
}
|
|
170
|
+
if (sub !== "decision") {
|
|
171
|
+
// eslint-disable-next-line no-console
|
|
172
|
+
console.error(`Unknown log subcommand: "${sub}". Use "tack log", "tack log decision", or "tack log events [N]".`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
const decisionText = args._.slice(2).join(" ").trim();
|
|
176
|
+
const reasonText = String(args.reason ?? "").trim();
|
|
177
|
+
if (!decisionText) {
|
|
178
|
+
// eslint-disable-next-line no-console
|
|
179
|
+
console.error('Missing decision text. Usage: tack log decision "Decision" --reason "Reason"');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
if (!reasonText) {
|
|
183
|
+
// eslint-disable-next-line no-console
|
|
184
|
+
console.error('Missing reason. Usage: tack log decision "Decision" --reason "Reason"');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
appendDecision(decisionText, reasonText);
|
|
189
|
+
log({
|
|
190
|
+
event: "decision",
|
|
191
|
+
decision: decisionText,
|
|
192
|
+
reasoning: reasonText,
|
|
193
|
+
actor: normalizeDecisionActor(typeof args.actor === "string" ? args.actor : undefined),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
printFatal(err);
|
|
198
|
+
}
|
|
199
|
+
// eslint-disable-next-line no-console
|
|
200
|
+
console.log("Decision logged.");
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
if (normalizedCommand === "note") {
|
|
204
|
+
const hasMessage = typeof args.message === "string" && args.message.trim().length > 0;
|
|
205
|
+
const hasClear = args.clear !== undefined;
|
|
206
|
+
if (hasMessage && hasClear) {
|
|
207
|
+
// eslint-disable-next-line no-console
|
|
208
|
+
console.error('Cannot use --message and --clear together. Use either "tack note --message ..." or "tack note --clear N".');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
if (!hasMessage && !hasClear) {
|
|
212
|
+
const limit = typeof args.limit === "number" ? args.limit : undefined;
|
|
213
|
+
const type = typeof args.type === "string" ? args.type : undefined;
|
|
214
|
+
printNotes({ limit, type });
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
if (hasMessage) {
|
|
218
|
+
const type = typeof args.type === "string" ? args.type : "discovered";
|
|
219
|
+
const actor = typeof args.actor === "string" ? args.actor : "user";
|
|
220
|
+
const ok = addNotePlain(type, String(args.message), actor);
|
|
221
|
+
if (ok) {
|
|
222
|
+
// eslint-disable-next-line no-console
|
|
223
|
+
console.log("Note added.");
|
|
224
|
+
}
|
|
225
|
+
process.exit(ok ? 0 : 1);
|
|
226
|
+
}
|
|
227
|
+
if (hasClear) {
|
|
228
|
+
const raw = args.clear;
|
|
229
|
+
const days = typeof raw === "number"
|
|
230
|
+
? raw
|
|
231
|
+
: typeof raw === "string"
|
|
232
|
+
? Number.parseInt(raw, 10)
|
|
233
|
+
: NaN;
|
|
234
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
235
|
+
// eslint-disable-next-line no-console
|
|
236
|
+
console.error('Invalid value for --clear. Expected a positive number of days, e.g. "tack note --clear 30".');
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
const archived = compactNotes(days);
|
|
240
|
+
// eslint-disable-next-line no-console
|
|
241
|
+
console.log(`Archived ${archived} notes older than ${days} days.`);
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!shouldUseInk) {
|
|
246
|
+
if (normalizedCommand === "init") {
|
|
247
|
+
try {
|
|
248
|
+
process.exit(runInitPlain() ? 0 : 1);
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
printFatal(err);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (normalizedCommand === "status") {
|
|
255
|
+
const result = runStatusScan();
|
|
256
|
+
if (!result) {
|
|
257
|
+
// eslint-disable-next-line no-console
|
|
258
|
+
console.error("No spec.yaml found. Run 'tack init' first.");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
printStatusPlain(result.status);
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
if (normalizedCommand === "handoff") {
|
|
265
|
+
try {
|
|
266
|
+
const generated = generateHandoff();
|
|
267
|
+
log({
|
|
268
|
+
event: "handoff",
|
|
269
|
+
markdown_path: generated.markdownPath,
|
|
270
|
+
json_path: generated.jsonPath,
|
|
271
|
+
});
|
|
272
|
+
printHandoffPlain(generated.markdownPath, generated.jsonPath, generated.report.generated_at);
|
|
273
|
+
process.exit(0);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
printFatal(err);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (normalizedCommand === "watch") {
|
|
280
|
+
try {
|
|
281
|
+
await runWatchPlain();
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
printFatal(err);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (normalizedCommand === "diff") {
|
|
289
|
+
try {
|
|
290
|
+
const baseBranch = args._[1];
|
|
291
|
+
const ok = runDiffPlain(baseBranch);
|
|
292
|
+
process.exit(ok ? 0 : 1);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
printFatal(err);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
render(_jsx(App, { command: normalizedCommand }));
|
package/dist/lib/cli.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Spec, type Audit, type DriftState } from "./signals.js";
|
|
2
|
+
export declare function projectRoot(): string;
|
|
3
|
+
export declare function findProjectRoot(): string;
|
|
4
|
+
export declare function writeSafe(filepath: string, content: string): void;
|
|
5
|
+
export declare function appendSafe(filepath: string, content: string): void;
|
|
6
|
+
export declare function ensureTackDir(): void;
|
|
7
|
+
export declare function readFile(filepath: string): string | null;
|
|
8
|
+
export declare function fileExists(filepath: string): boolean;
|
|
9
|
+
export declare function readJson<T = unknown>(filepath: string): T | null;
|
|
10
|
+
export declare function readYaml<T = unknown>(filepath: string): T | null;
|
|
11
|
+
export declare function listProjectFiles(dir?: string): string[];
|
|
12
|
+
export declare function grepFiles(files: string[], pattern: RegExp, maxResults?: number): Array<{
|
|
13
|
+
file: string;
|
|
14
|
+
line: number;
|
|
15
|
+
content: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function specPath(): string;
|
|
18
|
+
export declare function readSpec(): Spec | null;
|
|
19
|
+
export declare function readSpecWithError(): {
|
|
20
|
+
spec: Spec | null;
|
|
21
|
+
error: string | null;
|
|
22
|
+
};
|
|
23
|
+
export declare function writeSpec(spec: Spec): void;
|
|
24
|
+
export declare function specExists(): boolean;
|
|
25
|
+
export declare function auditPath(): string;
|
|
26
|
+
export declare function readAudit(): Audit | null;
|
|
27
|
+
export declare function writeAudit(audit: Audit): void;
|
|
28
|
+
export declare function driftPath(): string;
|
|
29
|
+
export declare function readDrift(): DriftState;
|
|
30
|
+
export declare function writeDrift(state: DriftState): void;
|
|
31
|
+
export declare function logsPath(): string;
|
|
32
|
+
export declare function notesPath(): string;
|
|
33
|
+
export declare function contextPath(): string;
|
|
34
|
+
export declare function goalsPath(): string;
|
|
35
|
+
export declare function assumptionsPath(): string;
|
|
36
|
+
export declare function openQuestionsPath(): string;
|
|
37
|
+
export declare function decisionsPath(): string;
|
|
38
|
+
export declare function implementationStatusPath(): string;
|
|
39
|
+
export declare function contextIndexPath(): string;
|
|
40
|
+
export declare function verificationPath(): string;
|
|
41
|
+
export declare function handoffsDirPath(): string;
|
|
42
|
+
export declare function handoffMarkdownPath(timestampId: string): string;
|
|
43
|
+
export declare function handoffJsonPath(timestampId: string): string;
|
|
44
|
+
export declare function ensureContextTemplates(): void;
|
|
45
|
+
export declare function ensureTackIntegrity(): {
|
|
46
|
+
repaired: string[];
|
|
47
|
+
};
|
|
48
|
+
export declare function seedSpecIfMissing(): boolean;
|