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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +232 -0
  3. package/dist/App.d.ts +5 -0
  4. package/dist/App.js +17 -0
  5. package/dist/detectors/admin.d.ts +2 -0
  6. package/dist/detectors/admin.js +33 -0
  7. package/dist/detectors/auth.d.ts +2 -0
  8. package/dist/detectors/auth.js +86 -0
  9. package/dist/detectors/database.d.ts +2 -0
  10. package/dist/detectors/database.js +96 -0
  11. package/dist/detectors/duplicates.d.ts +2 -0
  12. package/dist/detectors/duplicates.js +23 -0
  13. package/dist/detectors/exports.d.ts +2 -0
  14. package/dist/detectors/exports.js +30 -0
  15. package/dist/detectors/framework.d.ts +2 -0
  16. package/dist/detectors/framework.js +71 -0
  17. package/dist/detectors/index.d.ts +12 -0
  18. package/dist/detectors/index.js +128 -0
  19. package/dist/detectors/jobs.d.ts +2 -0
  20. package/dist/detectors/jobs.js +62 -0
  21. package/dist/detectors/multiuser.d.ts +2 -0
  22. package/dist/detectors/multiuser.js +55 -0
  23. package/dist/detectors/payments.d.ts +2 -0
  24. package/dist/detectors/payments.js +49 -0
  25. package/dist/detectors/rules/auth.yaml +24 -0
  26. package/dist/detectors/rules/database.yaml +27 -0
  27. package/dist/detectors/rules/exports.yaml +28 -0
  28. package/dist/detectors/rules/framework.yaml +26 -0
  29. package/dist/detectors/rules/jobs.yaml +23 -0
  30. package/dist/detectors/rules/payments.yaml +22 -0
  31. package/dist/detectors/types.d.ts +2 -0
  32. package/dist/detectors/types.js +1 -0
  33. package/dist/detectors/yamlRunner.d.ts +31 -0
  34. package/dist/detectors/yamlRunner.js +128 -0
  35. package/dist/engine/cleanup.d.ts +12 -0
  36. package/dist/engine/cleanup.js +101 -0
  37. package/dist/engine/compaction.d.ts +5 -0
  38. package/dist/engine/compaction.js +44 -0
  39. package/dist/engine/compareSpec.d.ts +2 -0
  40. package/dist/engine/compareSpec.js +74 -0
  41. package/dist/engine/computeDrift.d.ts +6 -0
  42. package/dist/engine/computeDrift.js +133 -0
  43. package/dist/engine/contextPack.d.ts +4 -0
  44. package/dist/engine/contextPack.js +169 -0
  45. package/dist/engine/decisions.d.ts +4 -0
  46. package/dist/engine/decisions.js +21 -0
  47. package/dist/engine/diff.d.ts +46 -0
  48. package/dist/engine/diff.js +210 -0
  49. package/dist/engine/handoff.d.ts +7 -0
  50. package/dist/engine/handoff.js +469 -0
  51. package/dist/engine/status.d.ts +10 -0
  52. package/dist/engine/status.js +46 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +299 -0
  55. package/dist/lib/cli.d.ts +4 -0
  56. package/dist/lib/cli.js +8 -0
  57. package/dist/lib/files.d.ts +48 -0
  58. package/dist/lib/files.js +529 -0
  59. package/dist/lib/git.d.ts +9 -0
  60. package/dist/lib/git.js +96 -0
  61. package/dist/lib/logger.d.ts +3 -0
  62. package/dist/lib/logger.js +21 -0
  63. package/dist/lib/ndjson.d.ts +2 -0
  64. package/dist/lib/ndjson.js +45 -0
  65. package/dist/lib/notes.d.ts +8 -0
  66. package/dist/lib/notes.js +144 -0
  67. package/dist/lib/notify.d.ts +1 -0
  68. package/dist/lib/notify.js +14 -0
  69. package/dist/lib/project.d.ts +1 -0
  70. package/dist/lib/project.js +17 -0
  71. package/dist/lib/promptSafety.d.ts +1 -0
  72. package/dist/lib/promptSafety.js +20 -0
  73. package/dist/lib/signals.d.ts +279 -0
  74. package/dist/lib/signals.js +55 -0
  75. package/dist/lib/tty.d.ts +2 -0
  76. package/dist/lib/tty.js +10 -0
  77. package/dist/lib/validate.d.ts +9 -0
  78. package/dist/lib/validate.js +282 -0
  79. package/dist/lib/yaml.d.ts +4 -0
  80. package/dist/lib/yaml.js +26 -0
  81. package/dist/mcp.d.ts +1 -0
  82. package/dist/mcp.js +259 -0
  83. package/dist/plain/colors.d.ts +5 -0
  84. package/dist/plain/colors.js +16 -0
  85. package/dist/plain/diff.d.ts +1 -0
  86. package/dist/plain/diff.js +129 -0
  87. package/dist/plain/handoff.d.ts +1 -0
  88. package/dist/plain/handoff.js +9 -0
  89. package/dist/plain/init.d.ts +1 -0
  90. package/dist/plain/init.js +44 -0
  91. package/dist/plain/notes.d.ts +5 -0
  92. package/dist/plain/notes.js +49 -0
  93. package/dist/plain/status.d.ts +2 -0
  94. package/dist/plain/status.js +13 -0
  95. package/dist/plain/watch.d.ts +1 -0
  96. package/dist/plain/watch.js +78 -0
  97. package/dist/ui/CleanupPlan.d.ts +5 -0
  98. package/dist/ui/CleanupPlan.js +8 -0
  99. package/dist/ui/DetectorSweep.d.ts +6 -0
  100. package/dist/ui/DetectorSweep.js +54 -0
  101. package/dist/ui/DriftAlert.d.ts +7 -0
  102. package/dist/ui/DriftAlert.js +105 -0
  103. package/dist/ui/Handoff.d.ts +1 -0
  104. package/dist/ui/Handoff.js +37 -0
  105. package/dist/ui/Init.d.ts +1 -0
  106. package/dist/ui/Init.js +117 -0
  107. package/dist/ui/Logo.d.ts +1 -0
  108. package/dist/ui/Logo.js +13 -0
  109. package/dist/ui/SpecSummary.d.ts +8 -0
  110. package/dist/ui/SpecSummary.js +15 -0
  111. package/dist/ui/Status.d.ts +1 -0
  112. package/dist/ui/Status.js +38 -0
  113. package/dist/ui/Watch.d.ts +1 -0
  114. package/dist/ui/Watch.js +136 -0
  115. package/dist/yoga.wasm +0 -0
  116. 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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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 }));
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Default command resolution: no-arg CLI runs init when .tack/ is missing, otherwise watch.
3
+ */
4
+ export declare function resolveDefaultCommand(raw: string | undefined, tackDirExists: boolean): "init" | "watch" | string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Default command resolution: no-arg CLI runs init when .tack/ is missing, otherwise watch.
3
+ */
4
+ export function resolveDefaultCommand(raw, tackDirExists) {
5
+ if (raw !== undefined && raw !== "")
6
+ return raw;
7
+ return tackDirExists ? "watch" : "init";
8
+ }
@@ -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;