@nick848/fet 0.1.0 → 1.0.1
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 -21
- package/README.md +265 -44
- package/dist/chunk-FZOVNHE7.js +104 -0
- package/dist/chunk-FZOVNHE7.js.map +1 -0
- package/dist/cli/index.js +1816 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -49
- package/dist/apply.d.ts +0 -1
- package/dist/apply.js +0 -172
- package/dist/approval.d.ts +0 -2
- package/dist/approval.js +0 -26
- package/dist/atomic-write.d.ts +0 -5
- package/dist/atomic-write.js +0 -41
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -178
- package/dist/doctor.d.ts +0 -1
- package/dist/doctor.js +0 -93
- package/dist/fingerprint.d.ts +0 -6
- package/dist/fingerprint.js +0 -77
- package/dist/hooks.d.ts +0 -12
- package/dist/hooks.js +0 -47
- package/dist/init.d.ts +0 -4
- package/dist/init.js +0 -47
- package/dist/opencode-skills.d.ts +0 -3
- package/dist/opencode-skills.js +0 -236
- package/dist/openspec.d.ts +0 -16
- package/dist/openspec.js +0 -73
- package/dist/paths.d.ts +0 -9
- package/dist/paths.js +0 -20
- package/dist/prompt.d.ts +0 -4
- package/dist/prompt.js +0 -30
- package/dist/scanner.d.ts +0 -23
- package/dist/scanner.js +0 -352
- package/dist/skills.d.ts +0 -3
- package/dist/skills.js +0 -142
- package/dist/state.d.ts +0 -17
- package/dist/state.js +0 -126
- package/dist/tasks.d.ts +0 -13
- package/dist/tasks.js +0 -69
- package/dist/types.d.ts +0 -38
- package/dist/types.js +0 -1
- package/dist/validate.d.ts +0 -1
- package/dist/validate.js +0 -150
- package/dist/verify.d.ts +0 -6
- package/dist/verify.js +0 -193
- package/dist/watch-paths.d.ts +0 -2
- package/dist/watch-paths.js +0 -70
- package/dist/workflow-hints.d.ts +0 -2
- package/dist/workflow-hints.js +0 -9
package/dist/atomic-write.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { closeSync, copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, renameSync, writeSync, } from "node:fs";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
3
|
-
/** DESIGN 14.5: same-dir `*.tmp` then rename (e.g. apply-instructions.md.tmp). */
|
|
4
|
-
export function writeFileAtomicSameDirTmp(targetPath, content) {
|
|
5
|
-
const dir = dirname(targetPath);
|
|
6
|
-
const base = basename(targetPath);
|
|
7
|
-
const tmp = join(dir, `${base}.tmp`);
|
|
8
|
-
if (!existsSync(dir))
|
|
9
|
-
mkdirSync(dir, { recursive: true });
|
|
10
|
-
const fd = openSync(tmp, "w");
|
|
11
|
-
try {
|
|
12
|
-
writeSync(fd, content);
|
|
13
|
-
fsyncSync(fd);
|
|
14
|
-
}
|
|
15
|
-
finally {
|
|
16
|
-
closeSync(fd);
|
|
17
|
-
}
|
|
18
|
-
renameSync(tmp, targetPath);
|
|
19
|
-
}
|
|
20
|
-
/** Write text atomically: temp file, fsync, rename (same directory). */
|
|
21
|
-
export function writeFileAtomicSync(targetPath, content) {
|
|
22
|
-
const dir = dirname(targetPath);
|
|
23
|
-
if (!existsSync(dir))
|
|
24
|
-
mkdirSync(dir, { recursive: true });
|
|
25
|
-
const tmp = `${targetPath}.${process.pid}.tmp`;
|
|
26
|
-
const fd = openSync(tmp, "w");
|
|
27
|
-
try {
|
|
28
|
-
writeSync(fd, content);
|
|
29
|
-
fsyncSync(fd);
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
closeSync(fd);
|
|
33
|
-
}
|
|
34
|
-
renameSync(tmp, targetPath);
|
|
35
|
-
}
|
|
36
|
-
export function backupIfExists(filePath) {
|
|
37
|
-
if (!existsSync(filePath))
|
|
38
|
-
return;
|
|
39
|
-
const bak = `${filePath}.bak`;
|
|
40
|
-
copyFileSync(filePath, bak);
|
|
41
|
-
}
|
package/dist/cli.d.ts
DELETED
package/dist/cli.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { Command } from "commander";
|
|
6
|
-
import { runApply } from "./apply.js";
|
|
7
|
-
import { runDoctor } from "./doctor.js";
|
|
8
|
-
import { assertChangeVerifiedForGate, runOpenSpecWithHooks } from "./hooks.js";
|
|
9
|
-
import { runInit } from "./init.js";
|
|
10
|
-
import { readGlobalState, positionalArgs } from "./state.js";
|
|
11
|
-
import { runUpdateContext } from "./scanner.js";
|
|
12
|
-
import { runValidate } from "./validate.js";
|
|
13
|
-
import { runVerify } from "./verify.js";
|
|
14
|
-
function tailAfter(sub) {
|
|
15
|
-
const a = process.argv;
|
|
16
|
-
const i = a.findIndex((v, idx) => idx >= 2 && v === sub);
|
|
17
|
-
if (i === -1)
|
|
18
|
-
return [];
|
|
19
|
-
return a.slice(i + 1);
|
|
20
|
-
}
|
|
21
|
-
function mapNewArgs(rest) {
|
|
22
|
-
if (rest[0] === "change")
|
|
23
|
-
return ["new", ...rest];
|
|
24
|
-
return ["new", "change", ...rest];
|
|
25
|
-
}
|
|
26
|
-
function readPkgVersion() {
|
|
27
|
-
try {
|
|
28
|
-
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
29
|
-
const raw = readFileSync(join(root, "package.json"), "utf8");
|
|
30
|
-
const j = JSON.parse(raw);
|
|
31
|
-
return j.version ?? "0.0.0";
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return "0.0.0";
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const PROXIES = [
|
|
38
|
-
"explore",
|
|
39
|
-
"propose",
|
|
40
|
-
"continue",
|
|
41
|
-
"ff",
|
|
42
|
-
"onboard",
|
|
43
|
-
"list",
|
|
44
|
-
"view",
|
|
45
|
-
"spec",
|
|
46
|
-
"config",
|
|
47
|
-
"show",
|
|
48
|
-
"update",
|
|
49
|
-
"feedback",
|
|
50
|
-
"status",
|
|
51
|
-
"instructions",
|
|
52
|
-
"templates",
|
|
53
|
-
"schemas",
|
|
54
|
-
"schema",
|
|
55
|
-
"change",
|
|
56
|
-
"completion",
|
|
57
|
-
];
|
|
58
|
-
const WORKFLOW_HINTS = new Set(["continue", "ff", "onboard"]);
|
|
59
|
-
async function main() {
|
|
60
|
-
const program = new Command();
|
|
61
|
-
program.name("fet").description("FET — OpenSpec workflow orchestration").version(readPkgVersion());
|
|
62
|
-
program
|
|
63
|
-
.command("init")
|
|
64
|
-
.option("--tool <tool>", "Tooling: cursor | opencode | both (OpenCode: .opencode/skills/*/SKILL.md, see opencode.ai/docs/skills)", "cursor")
|
|
65
|
-
.option("--force", "overwrite Cursor skills/rules", false)
|
|
66
|
-
.action(async (opts) => {
|
|
67
|
-
await runInit(process.cwd(), { tool: opts.tool, force: opts.force });
|
|
68
|
-
});
|
|
69
|
-
program.command("doctor").action(() => {
|
|
70
|
-
runDoctor(process.cwd());
|
|
71
|
-
});
|
|
72
|
-
program.command("update-context").action(() => {
|
|
73
|
-
runUpdateContext(process.cwd());
|
|
74
|
-
});
|
|
75
|
-
program.command("apply").action(async () => {
|
|
76
|
-
await runApply(process.cwd());
|
|
77
|
-
});
|
|
78
|
-
program.command("validate").action(async () => {
|
|
79
|
-
await runValidate(process.cwd());
|
|
80
|
-
});
|
|
81
|
-
program
|
|
82
|
-
.command("verify")
|
|
83
|
-
.description("Final verification. Default: honest local trust (DESIGN 14.8). --auto runs validate+lint+tsc+test; not a cryptographic audit.")
|
|
84
|
-
.option("--auto", "run automated verify chain", false)
|
|
85
|
-
.option("--done", "record manual verify complete", false)
|
|
86
|
-
.option("--evidence <path>", "optional evidence file for --done")
|
|
87
|
-
.option("--strict", "strict manual verify (recorded in state)", false)
|
|
88
|
-
.action(async (opts) => {
|
|
89
|
-
await runVerify(process.cwd(), {
|
|
90
|
-
auto: opts.auto,
|
|
91
|
-
done: opts.done,
|
|
92
|
-
evidence: opts.evidence,
|
|
93
|
-
strict: opts.strict,
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
program.command("archive").action(async () => {
|
|
97
|
-
try {
|
|
98
|
-
const rest = tailAfter("archive");
|
|
99
|
-
const pos = positionalArgs(rest);
|
|
100
|
-
const cwd = process.cwd();
|
|
101
|
-
const gateId = pos.length ? (pos[pos.length - 1] ?? null) : readGlobalState(cwd).activeChangeId;
|
|
102
|
-
const removeId = gateId ?? readGlobalState(cwd).activeChangeId;
|
|
103
|
-
const code = await runOpenSpecWithHooks({ cwd }, ["archive", ...rest], {
|
|
104
|
-
pre: () => {
|
|
105
|
-
assertChangeVerifiedForGate(cwd, gateId);
|
|
106
|
-
},
|
|
107
|
-
onSuccessRemoveChanges: removeId ? [removeId] : [],
|
|
108
|
-
});
|
|
109
|
-
process.exitCode = code;
|
|
110
|
-
}
|
|
111
|
-
catch (e) {
|
|
112
|
-
console.error(e instanceof Error ? e.message : e);
|
|
113
|
-
process.exitCode = 1;
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
program.command("bulk-archive").action(async () => {
|
|
117
|
-
try {
|
|
118
|
-
const rest = tailAfter("bulk-archive");
|
|
119
|
-
const ids = positionalArgs(rest);
|
|
120
|
-
const cwd = process.cwd();
|
|
121
|
-
if (!ids.length) {
|
|
122
|
-
console.error("[fet] bulk-archive requires at least one change id");
|
|
123
|
-
process.exitCode = 1;
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
for (const id of ids) {
|
|
127
|
-
assertChangeVerifiedForGate(cwd, id);
|
|
128
|
-
}
|
|
129
|
-
const code = await runOpenSpecWithHooks({ cwd }, ["bulk-archive", ...rest], { onSuccessRemoveChanges: ids });
|
|
130
|
-
process.exitCode = code;
|
|
131
|
-
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
console.error(e instanceof Error ? e.message : e);
|
|
134
|
-
process.exitCode = 1;
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
program.command("sync").action(async () => {
|
|
138
|
-
try {
|
|
139
|
-
if (!process.stdin.isTTY) {
|
|
140
|
-
console.error("[fet] sync requires an interactive terminal for OpenSpec conflict resolution (DESIGN 8.2).");
|
|
141
|
-
process.exitCode = 1;
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const rest = tailAfter("sync");
|
|
145
|
-
const cwd = process.cwd();
|
|
146
|
-
const pos = positionalArgs(rest);
|
|
147
|
-
const changeId = pos.length ? (pos[pos.length - 1] ?? null) : readGlobalState(cwd).activeChangeId;
|
|
148
|
-
const code = await runOpenSpecWithHooks({ cwd }, ["sync", ...rest], {
|
|
149
|
-
pre: () => {
|
|
150
|
-
assertChangeVerifiedForGate(cwd, changeId);
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
process.exitCode = code;
|
|
154
|
-
}
|
|
155
|
-
catch (e) {
|
|
156
|
-
console.error(e instanceof Error ? e.message : e);
|
|
157
|
-
process.exitCode = 1;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
program.command("new").action(async () => {
|
|
161
|
-
const rest = tailAfter("new");
|
|
162
|
-
const args = mapNewArgs(rest);
|
|
163
|
-
const code = await runOpenSpecWithHooks({ cwd: process.cwd() }, args, {});
|
|
164
|
-
process.exitCode = code;
|
|
165
|
-
});
|
|
166
|
-
for (const name of PROXIES) {
|
|
167
|
-
program.command(name).action(async () => {
|
|
168
|
-
const rest = tailAfter(name);
|
|
169
|
-
const code = await runOpenSpecWithHooks({ cwd: process.cwd() }, [name, ...rest], { workflowHint: WORKFLOW_HINTS.has(name) ? name : undefined });
|
|
170
|
-
process.exitCode = code;
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
await program.parseAsync(process.argv);
|
|
174
|
-
}
|
|
175
|
-
main().catch((e) => {
|
|
176
|
-
console.error(e);
|
|
177
|
-
process.exitCode = 1;
|
|
178
|
-
});
|
package/dist/doctor.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runDoctor(cwd: string): void;
|
package/dist/doctor.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { computeCommandFingerprint } from "./fingerprint.js";
|
|
4
|
-
import { getOpenSpecLaunch, openSpecVersionLine } from "./openspec.js";
|
|
5
|
-
import { CONFIG_FILE, GLOBAL_STATE_FILE, changeDir, tasksPath } from "./paths.js";
|
|
6
|
-
import { readChangeState, readGlobalState, reconcileStates, writeGlobalState } from "./state.js";
|
|
7
|
-
const STALE_MS = 1000 * 60 * 60 * 24 * 30;
|
|
8
|
-
const ARTIFACTS = ["proposal.md", "tasks.md", "design.md"];
|
|
9
|
-
export function runDoctor(cwd) {
|
|
10
|
-
const issues = [];
|
|
11
|
-
const hints = [];
|
|
12
|
-
const launch = getOpenSpecLaunch();
|
|
13
|
-
console.log(`[fet] openspec launch: ${launch.displayPath}`);
|
|
14
|
-
const ver = openSpecVersionLine();
|
|
15
|
-
if (ver)
|
|
16
|
-
console.log(`[fet] openspec version: ${ver}`);
|
|
17
|
-
else
|
|
18
|
-
hints.push("Could not read openspec -V (network or npx may be slow)");
|
|
19
|
-
const gs = join(cwd, GLOBAL_STATE_FILE);
|
|
20
|
-
if (!existsSync(gs))
|
|
21
|
-
issues.push(`missing ${GLOBAL_STATE_FILE}`);
|
|
22
|
-
else {
|
|
23
|
-
try {
|
|
24
|
-
JSON.parse(readFileSync(gs, "utf8"));
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
issues.push(`${GLOBAL_STATE_FILE} is not valid JSON`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const agents = join(cwd, "AGENTS.md");
|
|
31
|
-
if (existsSync(agents)) {
|
|
32
|
-
const mtime = statSync(agents).mtimeMs;
|
|
33
|
-
if (Date.now() - mtime > STALE_MS)
|
|
34
|
-
hints.push("AGENTS.md may be stale — consider fet update-context");
|
|
35
|
-
}
|
|
36
|
-
else
|
|
37
|
-
hints.push("AGENTS.md missing — run fet init / fet update-context");
|
|
38
|
-
const cfg = join(cwd, CONFIG_FILE);
|
|
39
|
-
if (existsSync(cfg)) {
|
|
40
|
-
const mtime = statSync(cfg).mtimeMs;
|
|
41
|
-
if (Date.now() - mtime > STALE_MS)
|
|
42
|
-
hints.push("openspec/config.yaml may be stale — consider fet update-context");
|
|
43
|
-
}
|
|
44
|
-
let g = readGlobalState(cwd);
|
|
45
|
-
const rec = reconcileStates(cwd, g);
|
|
46
|
-
g = rec.global;
|
|
47
|
-
writeGlobalState(cwd, g);
|
|
48
|
-
for (const w of rec.warnings)
|
|
49
|
-
hints.push(w);
|
|
50
|
-
const active = g.activeChangeId;
|
|
51
|
-
if (active) {
|
|
52
|
-
const cs = readChangeState(cwd, active);
|
|
53
|
-
if (cs?.verify?.status !== "done") {
|
|
54
|
-
hints.push(`Active change "${active}" is not verified — archive/sync will be blocked until fet verify --done or --auto`);
|
|
55
|
-
}
|
|
56
|
-
const changeRoot = join(cwd, changeDir(active));
|
|
57
|
-
if (existsSync(changeRoot)) {
|
|
58
|
-
for (const a of ARTIFACTS) {
|
|
59
|
-
const ap = join(changeRoot, a);
|
|
60
|
-
if (!existsSync(ap))
|
|
61
|
-
hints.push(`Change "${active}" missing artifact: ${a} (optional depending on workflow)`);
|
|
62
|
-
}
|
|
63
|
-
const tp = join(cwd, tasksPath(active));
|
|
64
|
-
if (!existsSync(tp))
|
|
65
|
-
hints.push(`Change "${active}" has no tasks.md yet`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const fp = computeCommandFingerprint(cwd);
|
|
69
|
-
if (g.autoRunApproval && g.autoRunApproval.commandFingerprint !== fp) {
|
|
70
|
-
hints.push("package.json scripts changed since last auto-run approval — next validate/verify --auto will re-prompt");
|
|
71
|
-
}
|
|
72
|
-
const cursorSkills = join(cwd, ".cursor", "skills");
|
|
73
|
-
if (!existsSync(cursorSkills))
|
|
74
|
-
hints.push("Cursor skills missing — run fet init --tool cursor");
|
|
75
|
-
const opencodeSkills = join(cwd, ".opencode", "skills");
|
|
76
|
-
if (!existsSync(opencodeSkills)) {
|
|
77
|
-
hints.push("OpenCode skills missing — run fet init --tool opencode (see https://opencode.ai/docs/skills )");
|
|
78
|
-
}
|
|
79
|
-
if (issues.length) {
|
|
80
|
-
console.error("[fet] doctor: issues:");
|
|
81
|
-
for (const i of issues)
|
|
82
|
-
console.error(` - ${i}`);
|
|
83
|
-
process.exitCode = 1;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
console.log("[fet] doctor: no blocking issues.");
|
|
87
|
-
}
|
|
88
|
-
if (hints.length) {
|
|
89
|
-
console.log("[fet] doctor: hints:");
|
|
90
|
-
for (const h of hints)
|
|
91
|
-
console.log(` - ${h}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
package/dist/fingerprint.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/** Collect script literals for validate/verify-auto (DESIGN 14.7, monorepo workspaces). */
|
|
2
|
-
export declare function collectScriptFingerprintParts(cwd: string): {
|
|
3
|
-
parts: string[];
|
|
4
|
-
lockfileHints: string[];
|
|
5
|
-
};
|
|
6
|
-
export declare function computeCommandFingerprint(cwd: string): string;
|
package/dist/fingerprint.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
-
import { join, relative } from "node:path";
|
|
4
|
-
function stableSerialize(obj) {
|
|
5
|
-
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
6
|
-
}
|
|
7
|
-
function workspacePackageJsonFiles(cwd) {
|
|
8
|
-
const out = [];
|
|
9
|
-
const pkgPath = join(cwd, "package.json");
|
|
10
|
-
if (!existsSync(pkgPath))
|
|
11
|
-
return out;
|
|
12
|
-
try {
|
|
13
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
14
|
-
const patterns = Array.isArray(pkg.workspaces)
|
|
15
|
-
? pkg.workspaces
|
|
16
|
-
: (pkg.workspaces?.packages ?? []);
|
|
17
|
-
for (const pat of patterns) {
|
|
18
|
-
if (typeof pat !== "string")
|
|
19
|
-
continue;
|
|
20
|
-
if (pat.includes("*")) {
|
|
21
|
-
const packagesDir = join(cwd, "packages");
|
|
22
|
-
if (existsSync(packagesDir)) {
|
|
23
|
-
for (const name of readdirSync(packagesDir)) {
|
|
24
|
-
const p = join(packagesDir, name, "package.json");
|
|
25
|
-
if (existsSync(p))
|
|
26
|
-
out.push(p);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
const p = join(cwd, pat, "package.json");
|
|
32
|
-
if (existsSync(p))
|
|
33
|
-
out.push(p);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
/* skip */
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
}
|
|
42
|
-
function scriptsFingerprintSlice(pkgPath, cwd) {
|
|
43
|
-
try {
|
|
44
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
45
|
-
const scripts = pkg.scripts ?? {};
|
|
46
|
-
const keys = ["test", "lint", "build"].filter((k) => scripts[k] != null);
|
|
47
|
-
const ordered = {};
|
|
48
|
-
for (const k of keys.sort())
|
|
49
|
-
ordered[k] = scripts[k] ?? "";
|
|
50
|
-
const rel = relative(cwd, pkgPath).replace(/\\/g, "/");
|
|
51
|
-
return `${rel}:${stableSerialize(ordered)}`;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return `${pkgPath}:{}`;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/** Collect script literals for validate/verify-auto (DESIGN 14.7, monorepo workspaces). */
|
|
58
|
-
export function collectScriptFingerprintParts(cwd) {
|
|
59
|
-
const parts = [];
|
|
60
|
-
const rootPkg = join(cwd, "package.json");
|
|
61
|
-
if (existsSync(rootPkg)) {
|
|
62
|
-
parts.push(scriptsFingerprintSlice(rootPkg, cwd));
|
|
63
|
-
}
|
|
64
|
-
for (const ws of workspacePackageJsonFiles(cwd)) {
|
|
65
|
-
parts.push(scriptsFingerprintSlice(ws, cwd));
|
|
66
|
-
}
|
|
67
|
-
const lockfiles = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"].filter((f) => existsSync(join(cwd, f)));
|
|
68
|
-
parts.push(...lockfiles.map((f) => `lock:${f}`));
|
|
69
|
-
return { parts, lockfileHints: lockfiles };
|
|
70
|
-
}
|
|
71
|
-
export function computeCommandFingerprint(cwd) {
|
|
72
|
-
const { parts } = collectScriptFingerprintParts(cwd);
|
|
73
|
-
const h = createHash("sha256");
|
|
74
|
-
for (const p of parts.sort())
|
|
75
|
-
h.update(p + "\n");
|
|
76
|
-
return h.digest("hex");
|
|
77
|
-
}
|
package/dist/hooks.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type HookContext = {
|
|
2
|
-
cwd: string;
|
|
3
|
-
};
|
|
4
|
-
export declare function runOpenSpecWithHooks(ctx: HookContext, openspecArgs: string[], hooks?: {
|
|
5
|
-
pre?: () => Promise<void> | void;
|
|
6
|
-
post?: (exitCode: number) => Promise<void> | void;
|
|
7
|
-
/** DESIGN 5.3: after exit 0, remove these change ids from global fet-state */
|
|
8
|
-
onSuccessRemoveChanges?: string[];
|
|
9
|
-
workflowHint?: string;
|
|
10
|
-
}): Promise<number>;
|
|
11
|
-
/** DESIGN 7.1 / 4: gate archive and sync on verify when using fet entry. */
|
|
12
|
-
export declare function assertChangeVerifiedForGate(cwd: string, changeId: string | null): void;
|
package/dist/hooks.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { printWorkflowHint } from "./workflow-hints.js";
|
|
4
|
-
import { spawnOpenSpec } from "./openspec.js";
|
|
5
|
-
import { changeDir } from "./paths.js";
|
|
6
|
-
import { readChangeState, readGlobalState, reconcileStates, removeArchivedChangesFromGlobal, writeGlobalState, } from "./state.js";
|
|
7
|
-
export async function runOpenSpecWithHooks(ctx, openspecArgs, hooks = {}) {
|
|
8
|
-
let global = readGlobalState(ctx.cwd);
|
|
9
|
-
const rec = reconcileStates(ctx.cwd, global);
|
|
10
|
-
global = rec.global;
|
|
11
|
-
if (rec.warnings.length) {
|
|
12
|
-
for (const w of rec.warnings)
|
|
13
|
-
console.error(`[fet] ${w}`);
|
|
14
|
-
}
|
|
15
|
-
writeGlobalState(ctx.cwd, global);
|
|
16
|
-
if (hooks.pre)
|
|
17
|
-
await hooks.pre();
|
|
18
|
-
const code = await spawnOpenSpec(openspecArgs, { cwd: ctx.cwd, inheritStdio: true });
|
|
19
|
-
if (hooks.post)
|
|
20
|
-
await hooks.post(code);
|
|
21
|
-
if (code === 0 && hooks.onSuccessRemoveChanges?.length) {
|
|
22
|
-
let g = readGlobalState(ctx.cwd);
|
|
23
|
-
g = removeArchivedChangesFromGlobal(g, hooks.onSuccessRemoveChanges);
|
|
24
|
-
writeGlobalState(ctx.cwd, g);
|
|
25
|
-
}
|
|
26
|
-
if (code === 0 && hooks.workflowHint)
|
|
27
|
-
printWorkflowHint(hooks.workflowHint);
|
|
28
|
-
const again = readGlobalState(ctx.cwd);
|
|
29
|
-
const rec2 = reconcileStates(ctx.cwd, again);
|
|
30
|
-
writeGlobalState(ctx.cwd, rec2.global);
|
|
31
|
-
return code;
|
|
32
|
-
}
|
|
33
|
-
/** DESIGN 7.1 / 4: gate archive and sync on verify when using fet entry. */
|
|
34
|
-
export function assertChangeVerifiedForGate(cwd, changeId) {
|
|
35
|
-
let id = changeId;
|
|
36
|
-
if (!id)
|
|
37
|
-
id = readGlobalState(cwd).activeChangeId;
|
|
38
|
-
if (!id)
|
|
39
|
-
return;
|
|
40
|
-
const changePath = join(cwd, changeDir(id));
|
|
41
|
-
if (!existsSync(changePath))
|
|
42
|
-
return;
|
|
43
|
-
const cs = readChangeState(cwd, id);
|
|
44
|
-
if (!cs || cs.verify?.status !== "done") {
|
|
45
|
-
throw new Error(`[fet] Change "${id}" is not verified. Complete fet verify (or fet verify --done) before archive/sync. Direct openspec calls are not gated.`);
|
|
46
|
-
}
|
|
47
|
-
}
|
package/dist/init.d.ts
DELETED
package/dist/init.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { getOpenSpecLaunch, resolveOpenSpecExecutable, spawnOpenSpec } from "./openspec.js";
|
|
4
|
-
import { GLOBAL_STATE_FILE } from "./paths.js";
|
|
5
|
-
import { defaultGlobalState, writeGlobalState } from "./state.js";
|
|
6
|
-
import { mergeConfigYaml, scanProject, writeAgentsMd } from "./scanner.js";
|
|
7
|
-
import { generateOpenCodeTooling } from "./opencode-skills.js";
|
|
8
|
-
import { generateCursorTooling } from "./skills.js";
|
|
9
|
-
export async function runInit(cwd, opts) {
|
|
10
|
-
const launch = getOpenSpecLaunch();
|
|
11
|
-
if (!resolveOpenSpecExecutable()) {
|
|
12
|
-
console.warn(`[fet] Global openspec not on PATH; using: ${launch.displayPath}`);
|
|
13
|
-
}
|
|
14
|
-
const code = await spawnOpenSpec(["init"], { cwd, inheritStdio: true });
|
|
15
|
-
if (code !== 0) {
|
|
16
|
-
process.exitCode = code;
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const openspecDir = join(cwd, "openspec");
|
|
20
|
-
if (!existsSync(openspecDir))
|
|
21
|
-
mkdirSync(openspecDir, { recursive: true });
|
|
22
|
-
const gsPath = join(cwd, GLOBAL_STATE_FILE);
|
|
23
|
-
if (!existsSync(gsPath)) {
|
|
24
|
-
writeGlobalState(cwd, defaultGlobalState());
|
|
25
|
-
}
|
|
26
|
-
const scan = scanProject(cwd);
|
|
27
|
-
for (const w of scan.warnings)
|
|
28
|
-
console.warn(`[fet] ${w}`);
|
|
29
|
-
writeAgentsMd(cwd, scan);
|
|
30
|
-
mergeConfigYaml(cwd, scan);
|
|
31
|
-
const tool = opts.tool ?? "cursor";
|
|
32
|
-
const force = opts.force ?? false;
|
|
33
|
-
if (tool === "cursor") {
|
|
34
|
-
await generateCursorTooling(cwd, { force });
|
|
35
|
-
}
|
|
36
|
-
else if (tool === "opencode") {
|
|
37
|
-
await generateOpenCodeTooling(cwd, { force });
|
|
38
|
-
}
|
|
39
|
-
else if (tool === "both") {
|
|
40
|
-
await generateCursorTooling(cwd, { force });
|
|
41
|
-
await generateOpenCodeTooling(cwd, { force });
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
console.warn(`[fet] Unknown --tool "${tool}". Use: cursor | opencode | both (see https://opencode.ai/docs/skills )`);
|
|
45
|
-
}
|
|
46
|
-
console.log("[fet] init complete.");
|
|
47
|
-
}
|