acstack 0.53.1 → 0.54.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/.claude-plugin/plugin.json +1 -1
- package/README.md +11 -11
- package/agents/codex/frontend-verifier.toml +8 -7
- package/agents/codex/task-checker.toml +1 -1
- package/agents/codex/task-executor.toml +1 -1
- package/agents/codex/test-writer.toml +1 -1
- package/agents/frontend-verifier.md +8 -7
- package/agents/task-checker.md +1 -1
- package/agents/task-executor.md +1 -1
- package/agents/test-writer.md +1 -1
- package/assets/playwriter-bridge/doctor.ps1 +1 -1
- package/assets/playwriter-bridge/register-tasks.ps1 +4 -4
- package/assets/terminal-workspace/config.example.yaml +2 -2
- package/assets/terminal-workspace/helpers/acstack-bridge +3 -1
- package/assets/terminal-workspace/helpers/acstack-db +1 -1
- package/assets/terminal-workspace/helpers/acstack-md +4 -2
- package/assets/terminal-workspace/helpers/acstack-md-lib +2 -2
- package/assets/terminal-workspace/helpers/acstack-open +1 -1
- package/assets/terminal-workspace/lib/apply-herdr-numbers.sh +1 -1
- package/assets/terminal-workspace/lib/apply-tools.sh +1 -1
- package/assets/terminal-workspace/lib/apply-tuicr.sh +2 -2
- package/assets/terminal-workspace/lib/commands.sh +1 -1
- package/assets/terminal-workspace/lib/helper-install.sh +1 -1
- package/assets/terminal-workspace/setup.sh +6 -1
- package/cli/dist/index.js +713 -523
- package/cli/dist/index.js.map +4 -4
- package/cli/package.json +1 -1
- package/docs/config-reference.md +129 -87
- package/docs/dev-setups.md +4 -4
- package/docs/terminal-workspace.md +7 -6
- package/docs/workflows.md +13 -12
- package/examples/design-doc/acstack/README.md +3 -3
- package/examples/design-doc/acstack/journey.md +1 -1
- package/examples/design-doc/acstack/publication.md +1 -1
- package/hooks/context-pressure.sh +6 -2
- package/hooks/lib/read-guard.mjs +25 -21
- package/hooks/project-data.sh +5 -34
- package/hooks/session-start-handoff.sh +11 -22
- package/hooks/session-start.sh +5 -6
- package/orchestrator/AGENTS.md +30 -25
- package/package.json +1 -1
- package/pi/agents/frontend-verifier.md +8 -7
- package/pi/agents/task-checker.md +1 -1
- package/pi/agents/task-executor.md +1 -1
- package/pi/agents/test-writer.md +1 -1
- package/pi/extension.ts +39 -57
- package/profiles/fullstack-python.yaml +1 -1
- package/profiles/node-typescript.yaml +1 -1
- package/profiles/python.yaml +1 -1
- package/skills/archive/SKILL.md +20 -18
- package/skills/bootstrap/SKILL.md +6 -6
- package/skills/bridge/SKILL-VM.md +5 -5
- package/skills/bridge/SKILL-WINDOWS.md +1 -1
- package/skills/browser-verify/SKILL.md +6 -6
- package/skills/commit/SKILL.md +8 -8
- package/skills/compact-and-continue/SKILL.md +11 -9
- package/skills/design-doc/SKILL.md +8 -5
- package/skills/explore/SKILL.md +1 -1
- package/skills/grilling/SKILL.md +4 -3
- package/skills/handoff/SKILL.md +4 -4
- package/skills/host/SKILL.md +2 -2
- package/skills/host/detect-host.sh +8 -4
- package/skills/host/herdr.md +1 -1
- package/skills/host/install-herdr-block.sh +1 -1
- package/skills/host/notes.md +5 -4
- package/skills/host/questions.md +1 -1
- package/skills/host/report.md +2 -2
- package/skills/implement/SKILL.md +6 -5
- package/skills/implement/decision-log.md +10 -15
- package/skills/implement/design-pass.md +2 -2
- package/skills/map/SKILL.md +5 -3
- package/skills/map/frontend.md +2 -2
- package/skills/map/materialising.md +5 -3
- package/skills/map/trackers/files.md +6 -4
- package/skills/pre-commit-review/SKILL.md +3 -3
- package/skills/quick-fix/SKILL.md +1 -1
- package/skills/record-context/SKILL.md +6 -6
- package/skills/setup/SKILL.md +20 -11
- package/skills/setup/browser.md +2 -2
- package/skills/setup/first-run.md +14 -16
- package/skills/setup/report.md +4 -3
- package/skills/setup/row-fixes.md +7 -8
- package/skills/sync/SKILL.md +14 -12
- package/skills/terminal-workspace/SKILL.md +4 -4
- package/skills/terminal-workspace/headless-clipboard.md +1 -1
- package/skills/terminal-workspace/turn-off-and-update.md +1 -1
- package/skills/test-conventions/SKILL.md +2 -2
package/cli/dist/index.js
CHANGED
|
@@ -10790,328 +10790,6 @@ var require_dist = __commonJS({
|
|
|
10790
10790
|
}
|
|
10791
10791
|
});
|
|
10792
10792
|
|
|
10793
|
-
// src/lib/atomic-file.ts
|
|
10794
|
-
import { randomBytes } from "node:crypto";
|
|
10795
|
-
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
10796
|
-
function tempPathFor(path) {
|
|
10797
|
-
return `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
10798
|
-
}
|
|
10799
|
-
function replaceFileAtomically(path, text2, beforeRename) {
|
|
10800
|
-
const temp = tempPathFor(path);
|
|
10801
|
-
try {
|
|
10802
|
-
writeFileSync(temp, text2, "utf-8");
|
|
10803
|
-
beforeRename?.(temp);
|
|
10804
|
-
renameSync(temp, path);
|
|
10805
|
-
} catch (err) {
|
|
10806
|
-
try {
|
|
10807
|
-
rmSync(temp, { force: true });
|
|
10808
|
-
} catch {
|
|
10809
|
-
}
|
|
10810
|
-
throw err;
|
|
10811
|
-
}
|
|
10812
|
-
}
|
|
10813
|
-
var init_atomic_file = __esm({
|
|
10814
|
-
"src/lib/atomic-file.ts"() {
|
|
10815
|
-
"use strict";
|
|
10816
|
-
}
|
|
10817
|
-
});
|
|
10818
|
-
|
|
10819
|
-
// src/lib/constants.ts
|
|
10820
|
-
import { join as join2 } from "node:path";
|
|
10821
|
-
import { homedir } from "node:os";
|
|
10822
|
-
function acstackFile(name, root) {
|
|
10823
|
-
const relative7 = join2(ACSTACK_DIR, name);
|
|
10824
|
-
return root === void 0 ? relative7 : join2(root, relative7);
|
|
10825
|
-
}
|
|
10826
|
-
function projectFile(root) {
|
|
10827
|
-
return acstackFile(PROJECT_FILE, root);
|
|
10828
|
-
}
|
|
10829
|
-
function domainMapFile(root) {
|
|
10830
|
-
return acstackFile(DOMAIN_MAP_FILE, root);
|
|
10831
|
-
}
|
|
10832
|
-
function claudeConfigRoot() {
|
|
10833
|
-
const stated = process.env.CLAUDE_CONFIG_DIR;
|
|
10834
|
-
return stated ? stated : join2(homedir(), CLAUDE_DIR);
|
|
10835
|
-
}
|
|
10836
|
-
var ACSTACK_DIR, LEGACY_ACSTACK_DIR, DECISIONS_DIR, DECISION_LOGS_DIR, VERIFICATION_DIR, CHANGES_DIR, MAPS_DIR, SPECS_DIR, ARCHIVE_DIR, PROJECT_FILE, DOMAIN_MAP_FILE, PROFILES_DIR, AGENT_BROWSER_JSON_FILE, VERIFICATION_KNOWLEDGE_FILE, AGENT_BROWSER_BIN, CLAUDE_DIR, CLAUDE_SETTINGS_FILE, CLAUDE_LOCAL_SETTINGS_FILE, ACSTACK_OUTPUT_STYLE, VOCABULARY_PATH, GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_FILE, GLOBAL_DATA_DIR;
|
|
10837
|
-
var init_constants = __esm({
|
|
10838
|
-
"src/lib/constants.ts"() {
|
|
10839
|
-
"use strict";
|
|
10840
|
-
ACSTACK_DIR = ".acstack";
|
|
10841
|
-
LEGACY_ACSTACK_DIR = "acstack";
|
|
10842
|
-
DECISIONS_DIR = "decisions";
|
|
10843
|
-
DECISION_LOGS_DIR = "decision-logs";
|
|
10844
|
-
VERIFICATION_DIR = "verification";
|
|
10845
|
-
CHANGES_DIR = "changes";
|
|
10846
|
-
MAPS_DIR = "maps";
|
|
10847
|
-
SPECS_DIR = "specs";
|
|
10848
|
-
ARCHIVE_DIR = "archive";
|
|
10849
|
-
PROJECT_FILE = "project.yaml";
|
|
10850
|
-
DOMAIN_MAP_FILE = "domain-map.yaml";
|
|
10851
|
-
PROFILES_DIR = "profiles";
|
|
10852
|
-
AGENT_BROWSER_JSON_FILE = join2(ACSTACK_DIR, "agent-browser.json");
|
|
10853
|
-
VERIFICATION_KNOWLEDGE_FILE = join2(ACSTACK_DIR, VERIFICATION_DIR, "VERIFICATION-KNOWLEDGE.md");
|
|
10854
|
-
AGENT_BROWSER_BIN = "agent-browser";
|
|
10855
|
-
CLAUDE_DIR = ".claude";
|
|
10856
|
-
CLAUDE_SETTINGS_FILE = "settings.json";
|
|
10857
|
-
CLAUDE_LOCAL_SETTINGS_FILE = "settings.local.json";
|
|
10858
|
-
ACSTACK_OUTPUT_STYLE = "acstack:ac-writing-style";
|
|
10859
|
-
VOCABULARY_PATH = join2("skills", "writing", "vocabulary.md");
|
|
10860
|
-
GLOBAL_CONFIG_DIR = join2(
|
|
10861
|
-
process.env.XDG_CONFIG_HOME ?? join2(homedir(), ".config"),
|
|
10862
|
-
"acstack"
|
|
10863
|
-
);
|
|
10864
|
-
GLOBAL_CONFIG_FILE = join2(GLOBAL_CONFIG_DIR, "config.json");
|
|
10865
|
-
GLOBAL_DATA_DIR = join2(
|
|
10866
|
-
process.env.XDG_DATA_HOME ?? join2(homedir(), ".local", "share"),
|
|
10867
|
-
"acstack"
|
|
10868
|
-
);
|
|
10869
|
-
}
|
|
10870
|
-
});
|
|
10871
|
-
|
|
10872
|
-
// src/lib/project.ts
|
|
10873
|
-
import { existsSync, lstatSync } from "node:fs";
|
|
10874
|
-
import { dirname as dirname2, join as join3, resolve } from "node:path";
|
|
10875
|
-
function findUp(startDir, marker) {
|
|
10876
|
-
let dir = resolve(startDir);
|
|
10877
|
-
while (true) {
|
|
10878
|
-
if (existsSync(join3(dir, marker))) return dir;
|
|
10879
|
-
const parent = dirname2(dir);
|
|
10880
|
-
if (parent === dir) return null;
|
|
10881
|
-
dir = parent;
|
|
10882
|
-
}
|
|
10883
|
-
}
|
|
10884
|
-
function hasLegacyProjectData(root) {
|
|
10885
|
-
const legacy = join3(root, LEGACY_ACSTACK_DIR);
|
|
10886
|
-
if (!lstatSync(legacy, { throwIfNoEntry: false })?.isDirectory()) return false;
|
|
10887
|
-
return [PROJECT_FILE, DOMAIN_MAP_FILE].some((name) => {
|
|
10888
|
-
const entry = lstatSync(join3(legacy, name), { throwIfNoEntry: false });
|
|
10889
|
-
return entry?.isFile() || entry?.isSymbolicLink();
|
|
10890
|
-
}) || ["alignment-packs", "design-reviews", MAPS_DIR, SPECS_DIR, CHANGES_DIR, ARCHIVE_DIR, DECISION_LOGS_DIR].some((name) => {
|
|
10891
|
-
const entry = lstatSync(join3(legacy, name), { throwIfNoEntry: false });
|
|
10892
|
-
return entry?.isDirectory() || entry?.isSymbolicLink();
|
|
10893
|
-
});
|
|
10894
|
-
}
|
|
10895
|
-
function findProjectRoot(from = process.cwd()) {
|
|
10896
|
-
let dir = resolve(from);
|
|
10897
|
-
while (true) {
|
|
10898
|
-
if (existsSync(join3(dir, ACSTACK_DIR)) || hasLegacyProjectData(dir)) return dir;
|
|
10899
|
-
const parent = dirname2(dir);
|
|
10900
|
-
if (parent === dir) return null;
|
|
10901
|
-
dir = parent;
|
|
10902
|
-
}
|
|
10903
|
-
}
|
|
10904
|
-
function findPluginRoot(startDir = import.meta.dirname) {
|
|
10905
|
-
const fromEnv = process.env.CLAUDE_PLUGIN_ROOT;
|
|
10906
|
-
if (fromEnv && existsSync(join3(fromEnv, PLUGIN_MARKER))) return resolve(fromEnv);
|
|
10907
|
-
return findUp(startDir, PLUGIN_MARKER);
|
|
10908
|
-
}
|
|
10909
|
-
var PLUGIN_MARKER;
|
|
10910
|
-
var init_project = __esm({
|
|
10911
|
-
"src/lib/project.ts"() {
|
|
10912
|
-
"use strict";
|
|
10913
|
-
init_constants();
|
|
10914
|
-
PLUGIN_MARKER = join3(".claude-plugin", "plugin.json");
|
|
10915
|
-
}
|
|
10916
|
-
});
|
|
10917
|
-
|
|
10918
|
-
// src/lib/project-data.ts
|
|
10919
|
-
import { chmodSync, existsSync as existsSync2, lstatSync as lstatSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, readlinkSync, realpathSync, renameSync as renameSync2, rmdirSync, symlinkSync, unlinkSync } from "node:fs";
|
|
10920
|
-
import { dirname as dirname3, isAbsolute, join as join4, relative, resolve as resolve2 } from "node:path";
|
|
10921
|
-
function present(path) {
|
|
10922
|
-
try {
|
|
10923
|
-
lstatSync2(path);
|
|
10924
|
-
return true;
|
|
10925
|
-
} catch (error) {
|
|
10926
|
-
if (error.code === "ENOENT") return false;
|
|
10927
|
-
throw error;
|
|
10928
|
-
}
|
|
10929
|
-
}
|
|
10930
|
-
function assertCanonicalProject(root) {
|
|
10931
|
-
if (!existsSync2(join4(root, ACSTACK_DIR)) && hasLegacyProjectData(root)) {
|
|
10932
|
-
throw new Error("Legacy ACStack project data found. Stop review servers and agents, back up the project, then run `acstack project-data migrate` from the project root.");
|
|
10933
|
-
}
|
|
10934
|
-
}
|
|
10935
|
-
function gitignorePlan(root) {
|
|
10936
|
-
const path = join4(root, ".gitignore");
|
|
10937
|
-
const stat = present(path) ? lstatSync2(path) : void 0;
|
|
10938
|
-
if (stat && (!stat.isFile() || stat.isSymbolicLink())) throw new Error(`${path}: .gitignore must be a regular file, not a symlink`);
|
|
10939
|
-
let before = "";
|
|
10940
|
-
if (stat) {
|
|
10941
|
-
try {
|
|
10942
|
-
before = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readFileSync2(path));
|
|
10943
|
-
} catch (error) {
|
|
10944
|
-
if (error instanceof TypeError) throw new Error(`${path}: invalid UTF-8; refusing to rewrite .gitignore`);
|
|
10945
|
-
throw error;
|
|
10946
|
-
}
|
|
10947
|
-
}
|
|
10948
|
-
const bom = before.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
10949
|
-
const body = before.slice(bom.length);
|
|
10950
|
-
const eol = body.includes("\r\n") ? "\r\n" : "\n";
|
|
10951
|
-
const lines = body.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
10952
|
-
const markers = lines.flatMap((line, index) => {
|
|
10953
|
-
const text2 = line.replace(/\r?\n$/, "");
|
|
10954
|
-
return text2.includes("acstack project-data") && (text2.includes(">>>") || text2.includes("<<<")) ? [{ text: text2, index }] : [];
|
|
10955
|
-
});
|
|
10956
|
-
if (markers.length !== 0 && (markers.length !== 2 || markers[0].text !== BEGIN || markers[1].text !== END)) {
|
|
10957
|
-
throw new Error(`${path}: malformed ACStack project-data managed markers`);
|
|
10958
|
-
}
|
|
10959
|
-
const outside = markers.length === 0 ? body : lines.filter((_, i) => i < markers[0].index || i > markers[1].index).join("");
|
|
10960
|
-
const baseline = [BEGIN, `!/${ACSTACK_DIR}/`, `/${ACSTACK_DIR}/**`, END, ""].join(eol);
|
|
10961
|
-
return { path, before, after: bom + baseline + outside, mode: stat?.mode };
|
|
10962
|
-
}
|
|
10963
|
-
function ensureProjectGitignore(root) {
|
|
10964
|
-
if (!present(join4(root, ACSTACK_DIR))) return;
|
|
10965
|
-
const plan = gitignorePlan(root);
|
|
10966
|
-
if (plan.before === plan.after) return;
|
|
10967
|
-
replaceFileAtomically(plan.path, plan.after, plan.mode === void 0 ? void 0 : (temp) => chmodSync(temp, plan.mode));
|
|
10968
|
-
}
|
|
10969
|
-
function inside(root, path) {
|
|
10970
|
-
const rel = relative(root, path);
|
|
10971
|
-
return rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !isAbsolute(rel);
|
|
10972
|
-
}
|
|
10973
|
-
function migrateProjectData(root) {
|
|
10974
|
-
root = resolve2(root);
|
|
10975
|
-
const legacy = join4(root, LEGACY_ACSTACK_DIR);
|
|
10976
|
-
const canonical = join4(root, ACSTACK_DIR);
|
|
10977
|
-
if (present(legacy) && (!lstatSync2(legacy).isDirectory() || lstatSync2(legacy).isSymbolicLink())) throw new Error(`${legacy}: legacy project data must be a real directory`);
|
|
10978
|
-
const candidates = [
|
|
10979
|
-
...hasLegacyProjectData(root) ? [{ from: legacy, to: canonical }] : [],
|
|
10980
|
-
{ from: join4(root, "docs", "adr"), to: join4(canonical, DECISIONS_DIR) },
|
|
10981
|
-
{ from: join4(root, "CONTEXT.md"), to: join4(canonical, "CONTEXT.md") },
|
|
10982
|
-
{ from: join4(root, "agent-browser.json"), to: join4(canonical, "agent-browser.json") },
|
|
10983
|
-
{ from: join4(root, ".test-baseline"), to: join4(canonical, ".test-baseline") },
|
|
10984
|
-
{ from: join4(root, ".decisions"), to: join4(canonical, DECISION_LOGS_DIR) },
|
|
10985
|
-
{ from: join4(root, KNOWLEDGE), to: join4(canonical, VERIFICATION_DIR, KNOWLEDGE) }
|
|
10986
|
-
];
|
|
10987
|
-
const config = join4(legacy, "project.yaml");
|
|
10988
|
-
if (hasLegacyProjectData(root) && existsSync2(config)) {
|
|
10989
|
-
const yaml = (0, import_yaml.parse)(readFileSync2(config, "utf8"));
|
|
10990
|
-
const helpers = yaml?.frontend?.helpers_dir;
|
|
10991
|
-
if (typeof helpers === "string") {
|
|
10992
|
-
const path = resolve2(root, helpers, KNOWLEDGE);
|
|
10993
|
-
if (inside(root, path) && present(path) && inside(realpathSync(root), realpathSync(dirname3(path)))) {
|
|
10994
|
-
if (!candidates.some((move) => move.from === path)) candidates.push({ from: path, to: join4(canonical, VERIFICATION_DIR, KNOWLEDGE) });
|
|
10995
|
-
}
|
|
10996
|
-
}
|
|
10997
|
-
}
|
|
10998
|
-
const moves = candidates.filter((move) => present(move.from));
|
|
10999
|
-
if (moves.length === 0) {
|
|
11000
|
-
ensureProjectGitignore(root);
|
|
11001
|
-
return { moved: [] };
|
|
11002
|
-
}
|
|
11003
|
-
if (present(canonical)) throw new Error(`${canonical}: migration destination already exists; refusing to merge or overwrite project data`);
|
|
11004
|
-
const ignore = gitignorePlan(root);
|
|
11005
|
-
const mapPath = (path) => {
|
|
11006
|
-
const move = [...moves].sort((a, b) => b.from.length - a.from.length).find((candidate) => inside(candidate.from, path));
|
|
11007
|
-
return move ? join4(move.to, relative(move.from, path)) : path;
|
|
11008
|
-
};
|
|
11009
|
-
for (const move of moves) {
|
|
11010
|
-
for (let parent = dirname3(move.from); parent !== root; parent = dirname3(parent)) {
|
|
11011
|
-
if (!inside(root, parent) || lstatSync2(parent).isSymbolicLink()) throw new Error(`${parent}: migration cannot traverse a symlinked source directory`);
|
|
11012
|
-
}
|
|
11013
|
-
for (const other of moves) {
|
|
11014
|
-
if (move === other) continue;
|
|
11015
|
-
if (move.to === other.to) throw new Error(`${move.to}: conflicting incoming project data`);
|
|
11016
|
-
if (inside(other.to, move.to)) {
|
|
11017
|
-
const incoming = join4(other.from, relative(other.to, move.to));
|
|
11018
|
-
if (present(incoming)) throw new Error(`${move.to}: destination conflicts with incoming legacy data`);
|
|
11019
|
-
for (let parent = dirname3(incoming); parent !== other.from; parent = dirname3(parent)) {
|
|
11020
|
-
if (present(parent) && (!lstatSync2(parent).isDirectory() || lstatSync2(parent).isSymbolicLink())) throw new Error(`${parent}: unsafe incoming destination parent`);
|
|
11021
|
-
}
|
|
11022
|
-
}
|
|
11023
|
-
}
|
|
11024
|
-
}
|
|
11025
|
-
const links = [];
|
|
11026
|
-
const removals = [];
|
|
11027
|
-
const ownedIgnores = /* @__PURE__ */ new Set([
|
|
11028
|
-
join4(legacy, "alignment-packs", ".gitignore"),
|
|
11029
|
-
join4(legacy, "design-reviews", ".gitignore"),
|
|
11030
|
-
join4(root, ".decisions", ".gitignore"),
|
|
11031
|
-
join4(legacy, DECISION_LOGS_DIR, ".gitignore")
|
|
11032
|
-
]);
|
|
11033
|
-
const visited = /* @__PURE__ */ new Set();
|
|
11034
|
-
const inspect = (path) => {
|
|
11035
|
-
if (visited.has(path)) return;
|
|
11036
|
-
visited.add(path);
|
|
11037
|
-
const stat = lstatSync2(path);
|
|
11038
|
-
if (stat.isSymbolicLink()) {
|
|
11039
|
-
const before = readlinkSync(path);
|
|
11040
|
-
const target = mapPath(resolve2(dirname3(path), before));
|
|
11041
|
-
const after = isAbsolute(before) ? target : relative(dirname3(mapPath(path)), target) || ".";
|
|
11042
|
-
if (before !== after) links.push({ path: mapPath(path), before, after });
|
|
11043
|
-
} else if (stat.isDirectory()) {
|
|
11044
|
-
for (const name of readdirSync(path)) inspect(join4(path, name));
|
|
11045
|
-
} else if (ownedIgnores.has(path) && stat.isFile()) {
|
|
11046
|
-
const text2 = readFileSync2(path, "utf8");
|
|
11047
|
-
const normalized = text2.replace(/\r\n/g, "\n");
|
|
11048
|
-
if (normalized === "*\n" || normalized === "*" || normalized === "# Written by acstack. Delete this file and set workflow.alignment_packs.commit_generated_packs: true to commit packs.\n*\n") {
|
|
11049
|
-
removals.push({ path: mapPath(path), text: text2, mode: stat.mode });
|
|
11050
|
-
}
|
|
11051
|
-
}
|
|
11052
|
-
};
|
|
11053
|
-
for (const move of moves) inspect(move.from);
|
|
11054
|
-
const undo = [];
|
|
11055
|
-
const makeParents = (path) => {
|
|
11056
|
-
if (present(path)) return;
|
|
11057
|
-
makeParents(dirname3(path));
|
|
11058
|
-
mkdirSync(path);
|
|
11059
|
-
undo.push(() => rmdirSync(path));
|
|
11060
|
-
};
|
|
11061
|
-
try {
|
|
11062
|
-
for (const move of moves) {
|
|
11063
|
-
const from = present(move.from) ? move.from : join4(canonical, relative(legacy, move.from));
|
|
11064
|
-
makeParents(dirname3(move.to));
|
|
11065
|
-
if (present(move.to)) throw new Error(`${move.to}: migration destination appeared during migration`);
|
|
11066
|
-
renameSync2(from, move.to);
|
|
11067
|
-
undo.push(() => renameSync2(move.to, from));
|
|
11068
|
-
}
|
|
11069
|
-
for (const link of links) {
|
|
11070
|
-
unlinkSync(link.path);
|
|
11071
|
-
try {
|
|
11072
|
-
symlinkSync(link.after, link.path);
|
|
11073
|
-
} catch (error) {
|
|
11074
|
-
symlinkSync(link.before, link.path);
|
|
11075
|
-
throw error;
|
|
11076
|
-
}
|
|
11077
|
-
undo.push(() => {
|
|
11078
|
-
unlinkSync(link.path);
|
|
11079
|
-
symlinkSync(link.before, link.path);
|
|
11080
|
-
});
|
|
11081
|
-
}
|
|
11082
|
-
for (const removal of removals) {
|
|
11083
|
-
unlinkSync(removal.path);
|
|
11084
|
-
undo.push(() => replaceFileAtomically(removal.path, removal.text, (temp) => chmodSync(temp, removal.mode)));
|
|
11085
|
-
}
|
|
11086
|
-
replaceFileAtomically(ignore.path, ignore.after, ignore.mode === void 0 ? void 0 : (temp) => chmodSync(temp, ignore.mode));
|
|
11087
|
-
return { moved: moves.map((move) => relative(root, move.to)) };
|
|
11088
|
-
} catch (error) {
|
|
11089
|
-
const failures = [];
|
|
11090
|
-
for (const rollback of undo.reverse()) {
|
|
11091
|
-
try {
|
|
11092
|
-
rollback();
|
|
11093
|
-
} catch (failure) {
|
|
11094
|
-
failures.push(String(failure));
|
|
11095
|
-
}
|
|
11096
|
-
}
|
|
11097
|
-
if (failures.length) throw new Error(`${String(error)}; rollback incomplete: ${failures.join("; ")}`);
|
|
11098
|
-
throw error;
|
|
11099
|
-
}
|
|
11100
|
-
}
|
|
11101
|
-
var import_yaml, BEGIN, END, KNOWLEDGE;
|
|
11102
|
-
var init_project_data = __esm({
|
|
11103
|
-
"src/lib/project-data.ts"() {
|
|
11104
|
-
"use strict";
|
|
11105
|
-
import_yaml = __toESM(require_dist(), 1);
|
|
11106
|
-
init_atomic_file();
|
|
11107
|
-
init_project();
|
|
11108
|
-
init_constants();
|
|
11109
|
-
BEGIN = "# >>> acstack project-data >>>";
|
|
11110
|
-
END = "# <<< acstack project-data <<<";
|
|
11111
|
-
KNOWLEDGE = "VERIFICATION-KNOWLEDGE.md";
|
|
11112
|
-
}
|
|
11113
|
-
});
|
|
11114
|
-
|
|
11115
10793
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
11116
10794
|
function assembleStyles() {
|
|
11117
10795
|
const codes = /* @__PURE__ */ new Map();
|
|
@@ -11619,18 +11297,190 @@ var init_source = __esm({
|
|
|
11619
11297
|
if (lfIndex !== -1) {
|
|
11620
11298
|
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
11621
11299
|
}
|
|
11622
|
-
return openAll + string + closeAll;
|
|
11300
|
+
return openAll + string + closeAll;
|
|
11301
|
+
};
|
|
11302
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
11303
|
+
chalk = createChalk();
|
|
11304
|
+
chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
11305
|
+
source_default = chalk;
|
|
11306
|
+
}
|
|
11307
|
+
});
|
|
11308
|
+
|
|
11309
|
+
// src/lib/constants.ts
|
|
11310
|
+
import { join as join2 } from "node:path";
|
|
11311
|
+
import { homedir } from "node:os";
|
|
11312
|
+
function acstackFile(name, root) {
|
|
11313
|
+
return join2(root ?? PROJECT_FOLDER_LABEL, name);
|
|
11314
|
+
}
|
|
11315
|
+
function projectFile(root) {
|
|
11316
|
+
return acstackFile(PROJECT_FILE, root);
|
|
11317
|
+
}
|
|
11318
|
+
function domainMapFile(root) {
|
|
11319
|
+
return acstackFile(DOMAIN_MAP_FILE, root);
|
|
11320
|
+
}
|
|
11321
|
+
function claudeConfigRoot() {
|
|
11322
|
+
const stated = process.env.CLAUDE_CONFIG_DIR;
|
|
11323
|
+
return stated ? stated : join2(homedir(), CLAUDE_DIR);
|
|
11324
|
+
}
|
|
11325
|
+
var ACSTACK_DIR, LEGACY_ACSTACK_DIR, DECISIONS_DIR, DECISION_LOGS_DIR, VERIFICATION_DIR, CHANGES_DIR, MAPS_DIR, SPECS_DIR, ARCHIVE_DIR, PROJECT_FILE, DOMAIN_MAP_FILE, PROJECT_FOLDER_LABEL, PROFILES_DIR, AGENT_BROWSER_JSON_FILE, VERIFICATION_KNOWLEDGE_FILE, AGENT_BROWSER_BIN, CLAUDE_DIR, CLAUDE_SETTINGS_FILE, CLAUDE_LOCAL_SETTINGS_FILE, ACSTACK_OUTPUT_STYLE, VOCABULARY_PATH, ACSTACK_HOME, GLOBAL_CONFIG_FILE, GLOBAL_CACHE_DIR, PROJECTS_DIR, PROJECT_JSON_FILE;
|
|
11326
|
+
var init_constants = __esm({
|
|
11327
|
+
"src/lib/constants.ts"() {
|
|
11328
|
+
"use strict";
|
|
11329
|
+
ACSTACK_DIR = ".acstack";
|
|
11330
|
+
LEGACY_ACSTACK_DIR = "acstack";
|
|
11331
|
+
DECISIONS_DIR = "decisions";
|
|
11332
|
+
DECISION_LOGS_DIR = "decision-logs";
|
|
11333
|
+
VERIFICATION_DIR = "verification";
|
|
11334
|
+
CHANGES_DIR = "changes";
|
|
11335
|
+
MAPS_DIR = "maps";
|
|
11336
|
+
SPECS_DIR = "specs";
|
|
11337
|
+
ARCHIVE_DIR = "archive";
|
|
11338
|
+
PROJECT_FILE = "project.yaml";
|
|
11339
|
+
DOMAIN_MAP_FILE = "domain-map.yaml";
|
|
11340
|
+
PROJECT_FOLDER_LABEL = "<project folder>";
|
|
11341
|
+
PROFILES_DIR = "profiles";
|
|
11342
|
+
AGENT_BROWSER_JSON_FILE = "agent-browser.json";
|
|
11343
|
+
VERIFICATION_KNOWLEDGE_FILE = join2(VERIFICATION_DIR, "VERIFICATION-KNOWLEDGE.md");
|
|
11344
|
+
AGENT_BROWSER_BIN = "agent-browser";
|
|
11345
|
+
CLAUDE_DIR = ".claude";
|
|
11346
|
+
CLAUDE_SETTINGS_FILE = "settings.json";
|
|
11347
|
+
CLAUDE_LOCAL_SETTINGS_FILE = "settings.local.json";
|
|
11348
|
+
ACSTACK_OUTPUT_STYLE = "acstack:ac-writing-style";
|
|
11349
|
+
VOCABULARY_PATH = join2("skills", "writing", "vocabulary.md");
|
|
11350
|
+
ACSTACK_HOME = process.env.ACSTACK_HOME || join2(homedir(), ".acstack");
|
|
11351
|
+
GLOBAL_CONFIG_FILE = join2(ACSTACK_HOME, "config.json");
|
|
11352
|
+
GLOBAL_CACHE_DIR = join2(ACSTACK_HOME, "cache");
|
|
11353
|
+
PROJECTS_DIR = join2(ACSTACK_HOME, "projects");
|
|
11354
|
+
PROJECT_JSON_FILE = "project.json";
|
|
11355
|
+
}
|
|
11356
|
+
});
|
|
11357
|
+
|
|
11358
|
+
// src/lib/project.ts
|
|
11359
|
+
import { spawnSync } from "node:child_process";
|
|
11360
|
+
import { createHash } from "node:crypto";
|
|
11361
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync as readFileSync2, readdirSync, realpathSync, symlinkSync, writeFileSync } from "node:fs";
|
|
11362
|
+
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
|
11363
|
+
function findUp(startDir, marker) {
|
|
11364
|
+
let dir = resolve(startDir);
|
|
11365
|
+
while (true) {
|
|
11366
|
+
if (existsSync(join3(dir, marker))) return dir;
|
|
11367
|
+
const parent = dirname2(dir);
|
|
11368
|
+
if (parent === dir) return null;
|
|
11369
|
+
dir = parent;
|
|
11370
|
+
}
|
|
11371
|
+
}
|
|
11372
|
+
function hasLegacyProjectData(root) {
|
|
11373
|
+
const legacy = join3(root, LEGACY_ACSTACK_DIR);
|
|
11374
|
+
if (!lstatSync(legacy, { throwIfNoEntry: false })?.isDirectory()) return false;
|
|
11375
|
+
return [PROJECT_FILE, DOMAIN_MAP_FILE].some((name) => {
|
|
11376
|
+
const entry = lstatSync(join3(legacy, name), { throwIfNoEntry: false });
|
|
11377
|
+
return entry?.isFile() || entry?.isSymbolicLink();
|
|
11378
|
+
}) || ["alignment-packs", "design-reviews", MAPS_DIR, SPECS_DIR, CHANGES_DIR, ARCHIVE_DIR, DECISION_LOGS_DIR].some((name) => {
|
|
11379
|
+
const entry = lstatSync(join3(legacy, name), { throwIfNoEntry: false });
|
|
11380
|
+
return entry?.isDirectory() || entry?.isSymbolicLink();
|
|
11381
|
+
});
|
|
11382
|
+
}
|
|
11383
|
+
function git(cwd, args) {
|
|
11384
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
11385
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
11386
|
+
}
|
|
11387
|
+
function checkoutRoot(from = process.cwd()) {
|
|
11388
|
+
return git(from, ["rev-parse", "--show-toplevel"]) || null;
|
|
11389
|
+
}
|
|
11390
|
+
function locateProject(from = process.cwd(), projects = PROJECTS_DIR) {
|
|
11391
|
+
const common = git(from, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
11392
|
+
if (!common) return null;
|
|
11393
|
+
const commonDir = realpathSync.native(common);
|
|
11394
|
+
const { main, bare } = mainCheckout(commonDir, from);
|
|
11395
|
+
const key2 = `${basename(main)}-${createHash("sha256").update(commonDir).digest("hex").slice(0, 6)}`;
|
|
11396
|
+
return { key: key2, commonDir, main, bare, entry: join3(projects, key2) };
|
|
11397
|
+
}
|
|
11398
|
+
function mainCheckout(commonDir, from) {
|
|
11399
|
+
if (basename(commonDir) === ".git") return { main: dirname2(commonDir), bare: false };
|
|
11400
|
+
const worktree = git(commonDir, ["config", "core.worktree"]);
|
|
11401
|
+
if (worktree) return { main: realpathSync.native(resolve(commonDir, worktree)), bare: false };
|
|
11402
|
+
if (git(commonDir, ["rev-parse", "--is-bare-repository"]) === "true") return { main: commonDir, bare: true };
|
|
11403
|
+
return { main: realpathSync.native(checkoutRoot(from) ?? from), bare: false };
|
|
11404
|
+
}
|
|
11405
|
+
function hasTrackedProject(location) {
|
|
11406
|
+
return !location.bare && existsSync(join3(location.main, ACSTACK_DIR, PROJECT_FILE)) && !!git(location.main, ["ls-files", "--", `${ACSTACK_DIR}/${PROJECT_FILE}`]);
|
|
11407
|
+
}
|
|
11408
|
+
function findOrphans(location) {
|
|
11409
|
+
const projects = dirname2(location.entry);
|
|
11410
|
+
if (!existsSync(projects)) return [];
|
|
11411
|
+
const prefix = `${basename(location.main)}-`;
|
|
11412
|
+
return readdirSync(projects).filter((name) => name.startsWith(prefix) && /^[0-9a-f]{6}$/.test(name.slice(prefix.length)) && name !== location.key).map((name) => join3(projects, name)).filter((path) => {
|
|
11413
|
+
if (lstatSync(path).isSymbolicLink()) return !existsSync(path);
|
|
11414
|
+
const recorded = recordedCommonDir(path);
|
|
11415
|
+
return recorded !== null && !existsSync(recorded);
|
|
11416
|
+
});
|
|
11417
|
+
}
|
|
11418
|
+
function orphanWarnings(location) {
|
|
11419
|
+
if (lstatSync(location.entry, { throwIfNoEntry: false })) return [];
|
|
11420
|
+
return findOrphans(location).map((orphan) => `${orphan} belonged to a repository that is no longer there. If that was this repository before it moved, run \`acstack project-data relink ${basename(orphan)}\`.`);
|
|
11421
|
+
}
|
|
11422
|
+
function recordedCommonDir(folder) {
|
|
11423
|
+
try {
|
|
11424
|
+
return JSON.parse(readFileSync2(join3(folder, PROJECT_JSON_FILE), "utf8")).commonDir ?? null;
|
|
11425
|
+
} catch {
|
|
11426
|
+
return null;
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
function recordProject(location) {
|
|
11430
|
+
writeFileSync(join3(location.entry, PROJECT_JSON_FILE), JSON.stringify({ commonDir: location.commonDir }, null, 2) + "\n");
|
|
11431
|
+
}
|
|
11432
|
+
function linkTracked(location) {
|
|
11433
|
+
mkdirSync(dirname2(location.entry), { recursive: true });
|
|
11434
|
+
symlinkSync(join3(location.main, ACSTACK_DIR), location.entry, process.platform === "win32" ? "junction" : "dir");
|
|
11435
|
+
}
|
|
11436
|
+
function findProjectRoot(from = process.cwd()) {
|
|
11437
|
+
const location = locateProject(from);
|
|
11438
|
+
if (!location) return null;
|
|
11439
|
+
const entry = lstatSync(location.entry, { throwIfNoEntry: false });
|
|
11440
|
+
if (entry?.isSymbolicLink()) {
|
|
11441
|
+
if (!existsSync(location.entry)) throw new ProjectDataError(`${location.entry} links to ${join3(location.main, ACSTACK_DIR)}, which does not exist. Restore it or run \`acstack project-data mode global\`.`);
|
|
11442
|
+
return realpathSync(location.entry);
|
|
11443
|
+
}
|
|
11444
|
+
if (entry) {
|
|
11445
|
+
if (hasTrackedProject(location)) throw new ProjectDataError(`This repository has both a project folder (${location.entry}) and a committed ${ACSTACK_DIR}/. Choose one with \`acstack project-data mode tracked\` or \`acstack project-data mode global\`.`);
|
|
11446
|
+
if (!existsSync(join3(location.entry, PROJECT_JSON_FILE))) recordProject(location);
|
|
11447
|
+
return realpathSync(location.entry);
|
|
11448
|
+
}
|
|
11449
|
+
if (hasTrackedProject(location)) {
|
|
11450
|
+
linkTracked(location);
|
|
11451
|
+
return realpathSync(location.entry);
|
|
11452
|
+
}
|
|
11453
|
+
if (hasLegacyData(location, from)) throw new ProjectDataError(LEGACY_MESSAGE);
|
|
11454
|
+
return null;
|
|
11455
|
+
}
|
|
11456
|
+
function hasLegacyData(location, from = process.cwd()) {
|
|
11457
|
+
const top = git(from, ["rev-parse", "--show-toplevel"]);
|
|
11458
|
+
return [location.main, ...top ? [top] : []].some((root) => existsSync(join3(root, ACSTACK_DIR)) || hasLegacyProjectData(root));
|
|
11459
|
+
}
|
|
11460
|
+
function findPluginRoot(startDir = import.meta.dirname) {
|
|
11461
|
+
const fromEnv = process.env.CLAUDE_PLUGIN_ROOT;
|
|
11462
|
+
if (fromEnv && existsSync(join3(fromEnv, PLUGIN_MARKER))) return resolve(fromEnv);
|
|
11463
|
+
return findUp(startDir, PLUGIN_MARKER);
|
|
11464
|
+
}
|
|
11465
|
+
var PLUGIN_MARKER, ProjectDataError, LEGACY_MESSAGE;
|
|
11466
|
+
var init_project = __esm({
|
|
11467
|
+
"src/lib/project.ts"() {
|
|
11468
|
+
"use strict";
|
|
11469
|
+
init_constants();
|
|
11470
|
+
PLUGIN_MARKER = join3(".claude-plugin", "plugin.json");
|
|
11471
|
+
ProjectDataError = class extends Error {
|
|
11472
|
+
constructor(message) {
|
|
11473
|
+
super(message);
|
|
11474
|
+
this.name = "ProjectDataError";
|
|
11475
|
+
}
|
|
11623
11476
|
};
|
|
11624
|
-
|
|
11625
|
-
chalk = createChalk();
|
|
11626
|
-
chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
11627
|
-
source_default = chalk;
|
|
11477
|
+
LEGACY_MESSAGE = "Legacy ACStack project data found. Stop review servers and agents, back up the project, then run `acstack project-data migrate` from the repository.";
|
|
11628
11478
|
}
|
|
11629
11479
|
});
|
|
11630
11480
|
|
|
11631
11481
|
// src/lib/utils.ts
|
|
11632
|
-
import { existsSync as
|
|
11633
|
-
import { join as
|
|
11482
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readSync, readdirSync as readdirSync2 } from "node:fs";
|
|
11483
|
+
import { join as join4 } from "node:path";
|
|
11634
11484
|
function fail(message, hint) {
|
|
11635
11485
|
console.error(source_default.red(message));
|
|
11636
11486
|
if (hint) console.error(source_default.dim(hint));
|
|
@@ -11640,37 +11490,37 @@ function errorHint(err) {
|
|
|
11640
11490
|
return err.hint;
|
|
11641
11491
|
}
|
|
11642
11492
|
function ensureDir(path) {
|
|
11643
|
-
if (!
|
|
11493
|
+
if (!existsSync2(path)) mkdirSync2(path, { recursive: true });
|
|
11644
11494
|
}
|
|
11645
11495
|
function readYaml(path) {
|
|
11646
|
-
if (!
|
|
11647
|
-
return (0,
|
|
11496
|
+
if (!existsSync2(path)) return null;
|
|
11497
|
+
return (0, import_yaml.parse)(readFileSync3(path, "utf-8"));
|
|
11648
11498
|
}
|
|
11649
11499
|
function readMarkdown(path) {
|
|
11650
|
-
if (!
|
|
11500
|
+
if (!existsSync2(path)) return null;
|
|
11651
11501
|
return readFileSync3(path, "utf-8");
|
|
11652
11502
|
}
|
|
11653
11503
|
function listChanges(root) {
|
|
11654
|
-
const dir =
|
|
11655
|
-
if (!
|
|
11504
|
+
const dir = join4(root, CHANGES_DIR);
|
|
11505
|
+
if (!existsSync2(dir)) return [];
|
|
11656
11506
|
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name !== ARCHIVE_DIR).map((e) => e.name);
|
|
11657
11507
|
}
|
|
11658
11508
|
function listSpecs(root) {
|
|
11659
|
-
const dir =
|
|
11660
|
-
if (!
|
|
11509
|
+
const dir = join4(root, SPECS_DIR);
|
|
11510
|
+
if (!existsSync2(dir)) return [];
|
|
11661
11511
|
return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
11662
11512
|
}
|
|
11663
11513
|
function requireProject(root) {
|
|
11664
11514
|
if (!root) fail("Not in an ACStack project. Run `/acstack:setup` first.");
|
|
11665
|
-
try {
|
|
11666
|
-
assertCanonicalProject(root);
|
|
11667
|
-
} catch (error) {
|
|
11668
|
-
fail(error.message);
|
|
11669
|
-
}
|
|
11670
11515
|
}
|
|
11671
11516
|
function inProject(action) {
|
|
11672
11517
|
return async (...args) => {
|
|
11673
|
-
|
|
11518
|
+
let root = null;
|
|
11519
|
+
try {
|
|
11520
|
+
root = findProjectRoot();
|
|
11521
|
+
} catch (err) {
|
|
11522
|
+
fail(err.message);
|
|
11523
|
+
}
|
|
11674
11524
|
requireProject(root);
|
|
11675
11525
|
try {
|
|
11676
11526
|
await action(root, ...args);
|
|
@@ -11722,14 +11572,13 @@ function stdinWentSilent(windowMs) {
|
|
|
11722
11572
|
err.hint = `Set ${STDIN_SILENCE_ENV} to widen the window, in milliseconds.`;
|
|
11723
11573
|
return err;
|
|
11724
11574
|
}
|
|
11725
|
-
var
|
|
11575
|
+
var import_yaml, NAP, NAP_MS, CHUNK_BYTES, DEFAULT_STDIN_SILENCE_MS, STDIN_SILENCE_ENV, TTY_REFUSAL;
|
|
11726
11576
|
var init_utils = __esm({
|
|
11727
11577
|
"src/lib/utils.ts"() {
|
|
11728
11578
|
"use strict";
|
|
11729
|
-
|
|
11579
|
+
import_yaml = __toESM(require_dist(), 1);
|
|
11730
11580
|
init_source();
|
|
11731
11581
|
init_constants();
|
|
11732
|
-
init_project_data();
|
|
11733
11582
|
init_project();
|
|
11734
11583
|
NAP = new Int32Array(new SharedArrayBuffer(4));
|
|
11735
11584
|
NAP_MS = 5;
|
|
@@ -11740,24 +11589,380 @@ var init_utils = __esm({
|
|
|
11740
11589
|
}
|
|
11741
11590
|
});
|
|
11742
11591
|
|
|
11592
|
+
// src/lib/atomic-file.ts
|
|
11593
|
+
import { randomBytes } from "node:crypto";
|
|
11594
|
+
import { renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
11595
|
+
function tempPathFor(path) {
|
|
11596
|
+
return `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
11597
|
+
}
|
|
11598
|
+
function replaceFileAtomically(path, text2, beforeRename) {
|
|
11599
|
+
const temp = tempPathFor(path);
|
|
11600
|
+
try {
|
|
11601
|
+
writeFileSync2(temp, text2, "utf-8");
|
|
11602
|
+
beforeRename?.(temp);
|
|
11603
|
+
renameSync(temp, path);
|
|
11604
|
+
} catch (err) {
|
|
11605
|
+
try {
|
|
11606
|
+
rmSync(temp, { force: true });
|
|
11607
|
+
} catch {
|
|
11608
|
+
}
|
|
11609
|
+
throw err;
|
|
11610
|
+
}
|
|
11611
|
+
}
|
|
11612
|
+
var init_atomic_file = __esm({
|
|
11613
|
+
"src/lib/atomic-file.ts"() {
|
|
11614
|
+
"use strict";
|
|
11615
|
+
}
|
|
11616
|
+
});
|
|
11617
|
+
|
|
11618
|
+
// src/lib/project-data.ts
|
|
11619
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
11620
|
+
import { chmodSync, cpSync, existsSync as existsSync3, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync3, readlinkSync, realpathSync as realpathSync2, renameSync as renameSync2, rmSync as rmSync2, rmdirSync, symlinkSync as symlinkSync2, unlinkSync } from "node:fs";
|
|
11621
|
+
import { homedir as homedir2 } from "node:os";
|
|
11622
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join as join5, relative, resolve as resolve2 } from "node:path";
|
|
11623
|
+
function present(path) {
|
|
11624
|
+
try {
|
|
11625
|
+
lstatSync2(path);
|
|
11626
|
+
return true;
|
|
11627
|
+
} catch (error) {
|
|
11628
|
+
if (error.code === "ENOENT") return false;
|
|
11629
|
+
throw error;
|
|
11630
|
+
}
|
|
11631
|
+
}
|
|
11632
|
+
function removeManagedGitignoreBlock(root) {
|
|
11633
|
+
const path = join5(root, ".gitignore");
|
|
11634
|
+
const stat = lstatSync2(path, { throwIfNoEntry: false });
|
|
11635
|
+
if (!stat?.isFile()) return;
|
|
11636
|
+
const before = readFileSync4(path, "utf8");
|
|
11637
|
+
const lines = before.split(/(?<=\n)/);
|
|
11638
|
+
const begin = lines.findIndex((line) => line.trimEnd() === BEGIN);
|
|
11639
|
+
const end = lines.findIndex((line) => line.trimEnd() === END);
|
|
11640
|
+
if (begin === -1 || end < begin) return;
|
|
11641
|
+
const after = [...lines.slice(0, begin), ...lines.slice(end + 1)].join("").replace(/^(\r?\n)+/, "");
|
|
11642
|
+
replaceFileAtomically(path, after, (temp) => chmodSync(temp, stat.mode));
|
|
11643
|
+
}
|
|
11644
|
+
function inside(root, path) {
|
|
11645
|
+
const rel = relative(root, path);
|
|
11646
|
+
return rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !isAbsolute(rel);
|
|
11647
|
+
}
|
|
11648
|
+
function migrateOldLayout(root) {
|
|
11649
|
+
root = resolve2(root);
|
|
11650
|
+
const legacy = join5(root, LEGACY_ACSTACK_DIR);
|
|
11651
|
+
const canonical = join5(root, ACSTACK_DIR);
|
|
11652
|
+
if (present(legacy) && (!lstatSync2(legacy).isDirectory() || lstatSync2(legacy).isSymbolicLink())) throw new Error(`${legacy}: legacy project data must be a real directory`);
|
|
11653
|
+
const candidates = [
|
|
11654
|
+
...hasLegacyProjectData(root) ? [{ from: legacy, to: canonical }] : [],
|
|
11655
|
+
{ from: join5(root, "docs", "adr"), to: join5(canonical, DECISIONS_DIR) },
|
|
11656
|
+
{ from: join5(root, "CONTEXT.md"), to: join5(canonical, "CONTEXT.md") },
|
|
11657
|
+
{ from: join5(root, "agent-browser.json"), to: join5(canonical, "agent-browser.json") },
|
|
11658
|
+
{ from: join5(root, ".test-baseline"), to: join5(canonical, ".test-baseline") },
|
|
11659
|
+
{ from: join5(root, ".decisions"), to: join5(canonical, DECISION_LOGS_DIR) },
|
|
11660
|
+
{ from: join5(root, KNOWLEDGE), to: join5(canonical, VERIFICATION_DIR, KNOWLEDGE) }
|
|
11661
|
+
];
|
|
11662
|
+
const config = join5(legacy, "project.yaml");
|
|
11663
|
+
if (hasLegacyProjectData(root) && existsSync3(config)) {
|
|
11664
|
+
const yaml = (0, import_yaml2.parse)(readFileSync4(config, "utf8"));
|
|
11665
|
+
const helpers = yaml?.frontend?.helpers_dir;
|
|
11666
|
+
if (typeof helpers === "string") {
|
|
11667
|
+
const path = resolve2(root, helpers, KNOWLEDGE);
|
|
11668
|
+
if (inside(root, path) && present(path) && inside(realpathSync2(root), realpathSync2(dirname3(path)))) {
|
|
11669
|
+
if (!candidates.some((move) => move.from === path)) candidates.push({ from: path, to: join5(canonical, VERIFICATION_DIR, KNOWLEDGE) });
|
|
11670
|
+
}
|
|
11671
|
+
}
|
|
11672
|
+
}
|
|
11673
|
+
const moves = candidates.filter((move) => present(move.from));
|
|
11674
|
+
if (moves.length === 0) return { moved: [] };
|
|
11675
|
+
if (present(canonical)) throw new Error(`${canonical}: migration destination already exists; refusing to merge or overwrite project data`);
|
|
11676
|
+
const mapPath = (path) => {
|
|
11677
|
+
const move = [...moves].sort((a, b) => b.from.length - a.from.length).find((candidate) => inside(candidate.from, path));
|
|
11678
|
+
return move ? join5(move.to, relative(move.from, path)) : path;
|
|
11679
|
+
};
|
|
11680
|
+
for (const move of moves) {
|
|
11681
|
+
for (let parent = dirname3(move.from); parent !== root; parent = dirname3(parent)) {
|
|
11682
|
+
if (!inside(root, parent) || lstatSync2(parent).isSymbolicLink()) throw new Error(`${parent}: migration cannot traverse a symlinked source directory`);
|
|
11683
|
+
}
|
|
11684
|
+
for (const other of moves) {
|
|
11685
|
+
if (move === other) continue;
|
|
11686
|
+
if (move.to === other.to) throw new Error(`${move.to}: conflicting incoming project data`);
|
|
11687
|
+
if (inside(other.to, move.to)) {
|
|
11688
|
+
const incoming = join5(other.from, relative(other.to, move.to));
|
|
11689
|
+
if (present(incoming)) throw new Error(`${move.to}: destination conflicts with incoming legacy data`);
|
|
11690
|
+
for (let parent = dirname3(incoming); parent !== other.from; parent = dirname3(parent)) {
|
|
11691
|
+
if (present(parent) && (!lstatSync2(parent).isDirectory() || lstatSync2(parent).isSymbolicLink())) throw new Error(`${parent}: unsafe incoming destination parent`);
|
|
11692
|
+
}
|
|
11693
|
+
}
|
|
11694
|
+
}
|
|
11695
|
+
}
|
|
11696
|
+
const links = [];
|
|
11697
|
+
const removals = [];
|
|
11698
|
+
const ownedIgnores = /* @__PURE__ */ new Set([
|
|
11699
|
+
join5(legacy, "alignment-packs", ".gitignore"),
|
|
11700
|
+
join5(legacy, "design-reviews", ".gitignore"),
|
|
11701
|
+
join5(root, ".decisions", ".gitignore"),
|
|
11702
|
+
join5(legacy, DECISION_LOGS_DIR, ".gitignore")
|
|
11703
|
+
]);
|
|
11704
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11705
|
+
const inspect = (path) => {
|
|
11706
|
+
if (visited.has(path)) return;
|
|
11707
|
+
visited.add(path);
|
|
11708
|
+
const stat = lstatSync2(path);
|
|
11709
|
+
if (stat.isSymbolicLink()) {
|
|
11710
|
+
const before = readlinkSync(path);
|
|
11711
|
+
const target = mapPath(resolve2(dirname3(path), before));
|
|
11712
|
+
const after = isAbsolute(before) ? target : relative(dirname3(mapPath(path)), target) || ".";
|
|
11713
|
+
if (before !== after) links.push({ path: mapPath(path), before, after });
|
|
11714
|
+
} else if (stat.isDirectory()) {
|
|
11715
|
+
for (const name of readdirSync3(path)) inspect(join5(path, name));
|
|
11716
|
+
} else if (ownedIgnores.has(path) && stat.isFile()) {
|
|
11717
|
+
const text2 = readFileSync4(path, "utf8");
|
|
11718
|
+
const normalized = text2.replace(/\r\n/g, "\n");
|
|
11719
|
+
if (normalized === "*\n" || normalized === "*" || normalized === "# Written by acstack. Delete this file and set workflow.alignment_packs.commit_generated_packs: true to commit packs.\n*\n") {
|
|
11720
|
+
removals.push({ path: mapPath(path), text: text2, mode: stat.mode });
|
|
11721
|
+
}
|
|
11722
|
+
}
|
|
11723
|
+
};
|
|
11724
|
+
for (const move of moves) inspect(move.from);
|
|
11725
|
+
const undo = [];
|
|
11726
|
+
const makeParents = (path) => {
|
|
11727
|
+
if (present(path)) return;
|
|
11728
|
+
makeParents(dirname3(path));
|
|
11729
|
+
mkdirSync3(path);
|
|
11730
|
+
undo.push(() => rmdirSync(path));
|
|
11731
|
+
};
|
|
11732
|
+
try {
|
|
11733
|
+
for (const move of moves) {
|
|
11734
|
+
const from = present(move.from) ? move.from : join5(canonical, relative(legacy, move.from));
|
|
11735
|
+
makeParents(dirname3(move.to));
|
|
11736
|
+
if (present(move.to)) throw new Error(`${move.to}: migration destination appeared during migration`);
|
|
11737
|
+
renameSync2(from, move.to);
|
|
11738
|
+
undo.push(() => renameSync2(move.to, from));
|
|
11739
|
+
}
|
|
11740
|
+
for (const link of links) {
|
|
11741
|
+
unlinkSync(link.path);
|
|
11742
|
+
try {
|
|
11743
|
+
symlinkSync2(link.after, link.path);
|
|
11744
|
+
} catch (error) {
|
|
11745
|
+
symlinkSync2(link.before, link.path);
|
|
11746
|
+
throw error;
|
|
11747
|
+
}
|
|
11748
|
+
undo.push(() => {
|
|
11749
|
+
unlinkSync(link.path);
|
|
11750
|
+
symlinkSync2(link.before, link.path);
|
|
11751
|
+
});
|
|
11752
|
+
}
|
|
11753
|
+
for (const removal of removals) {
|
|
11754
|
+
unlinkSync(removal.path);
|
|
11755
|
+
undo.push(() => replaceFileAtomically(removal.path, removal.text, (temp) => chmodSync(temp, removal.mode)));
|
|
11756
|
+
}
|
|
11757
|
+
return { moved: moves.map((move) => relative(root, move.to)) };
|
|
11758
|
+
} catch (error) {
|
|
11759
|
+
const failures = [];
|
|
11760
|
+
for (const rollback of undo.reverse()) {
|
|
11761
|
+
try {
|
|
11762
|
+
rollback();
|
|
11763
|
+
} catch (failure) {
|
|
11764
|
+
failures.push(String(failure));
|
|
11765
|
+
}
|
|
11766
|
+
}
|
|
11767
|
+
if (failures.length) throw new Error(`${String(error)}; rollback incomplete: ${failures.join("; ")}`);
|
|
11768
|
+
throw error;
|
|
11769
|
+
}
|
|
11770
|
+
}
|
|
11771
|
+
function git2(cwd, args) {
|
|
11772
|
+
const result = spawnSync2("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
11773
|
+
if (result.status !== 0) throw new Error(`git ${args.join(" ")} failed: ${result.stderr.trim()}`);
|
|
11774
|
+
return result.stdout;
|
|
11775
|
+
}
|
|
11776
|
+
function requireLocation(from) {
|
|
11777
|
+
const location = locateProject(from);
|
|
11778
|
+
if (!location) throw new Error("Not in a Git repository; ACStack keys project data by repository.");
|
|
11779
|
+
return location;
|
|
11780
|
+
}
|
|
11781
|
+
function moveTree(from, to) {
|
|
11782
|
+
mkdirSync3(dirname3(to), { recursive: true });
|
|
11783
|
+
try {
|
|
11784
|
+
renameSync2(from, to);
|
|
11785
|
+
} catch (error) {
|
|
11786
|
+
if (error.code !== "EXDEV") throw error;
|
|
11787
|
+
const temp = `${to}.acstack-${process.pid}`;
|
|
11788
|
+
try {
|
|
11789
|
+
cpSync(from, temp, { recursive: true, verbatimSymlinks: true, errorOnExist: true, force: false });
|
|
11790
|
+
} catch (copyError) {
|
|
11791
|
+
rmSync2(temp, { recursive: true, force: true });
|
|
11792
|
+
throw copyError;
|
|
11793
|
+
}
|
|
11794
|
+
renameSync2(temp, to);
|
|
11795
|
+
rmSync2(from, { recursive: true });
|
|
11796
|
+
}
|
|
11797
|
+
}
|
|
11798
|
+
function safeStranded(location) {
|
|
11799
|
+
try {
|
|
11800
|
+
return strandedWorktrees(location);
|
|
11801
|
+
} catch (error) {
|
|
11802
|
+
return [`(could not list worktrees: ${error.message})`];
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11805
|
+
function requireRealDirectory(path) {
|
|
11806
|
+
if (!lstatSync2(path).isDirectory()) throw new Error(`${path} is not a real folder; move its contents into a folder named ${ACSTACK_DIR} first.`);
|
|
11807
|
+
}
|
|
11808
|
+
function strandedWorktrees(location) {
|
|
11809
|
+
const paths = git2(location.main, ["worktree", "list", "--porcelain"]).split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length));
|
|
11810
|
+
return paths.filter((path) => existsSync3(path) && realpathSync2(path) !== realpathSync2(location.main) && git2(path, ["status", "--porcelain", "--ignored", "--untracked-files=all", "--", ACSTACK_DIR]).trim() !== "");
|
|
11811
|
+
}
|
|
11812
|
+
function legacyGlobalMoves(home = ACSTACK_HOME) {
|
|
11813
|
+
const config = join5(process.env.XDG_CONFIG_HOME || join5(homedir2(), ".config"), "acstack");
|
|
11814
|
+
const data = join5(process.env.XDG_DATA_HOME || join5(homedir2(), ".local", "share"), "acstack");
|
|
11815
|
+
const children = (dir, to) => existsSync3(dir) ? readdirSync3(dir).map((name) => ({ from: join5(dir, name), to: join5(to, name) })) : [];
|
|
11816
|
+
return [...children(config, home), ...children(data, join5(home, "cache"))];
|
|
11817
|
+
}
|
|
11818
|
+
function legacyWarnings(from = process.cwd()) {
|
|
11819
|
+
const warnings = legacyGlobalMoves().length ? ["ACStack settings are still in ~/.config/acstack or ~/.local/share/acstack."] : [];
|
|
11820
|
+
const location = locateProject(from);
|
|
11821
|
+
if (location && !present(location.entry) && !hasTrackedProject(location) && (present(join5(location.main, ACSTACK_DIR)) || hasLegacyProjectData(location.main))) warnings.push(`${location.main} still holds its project data in the repository.`);
|
|
11822
|
+
return warnings.length ? [...warnings, "ACStack no longer reads these locations. Stop agents and review servers, back up, then run `acstack project-data migrate` from each repository."] : [];
|
|
11823
|
+
}
|
|
11824
|
+
function migrateProjectData(from) {
|
|
11825
|
+
const location = requireLocation(from);
|
|
11826
|
+
const global2 = legacyGlobalMoves();
|
|
11827
|
+
for (const move of global2) if (present(move.to)) throw new Error(`${move.to}: migration destination already exists; refusing to overwrite`);
|
|
11828
|
+
const top = checkoutRoot(from);
|
|
11829
|
+
const mainData = join5(location.main, ACSTACK_DIR);
|
|
11830
|
+
const repoData = !present(mainData) && top && top !== location.main && present(join5(top, ACSTACK_DIR)) ? join5(top, ACSTACK_DIR) : mainData;
|
|
11831
|
+
const source = dirname3(repoData);
|
|
11832
|
+
if (present(repoData)) requireRealDirectory(repoData);
|
|
11833
|
+
const oldLayout = !present(repoData) && (hasLegacyProjectData(location.main) || ["CONTEXT.md", join5("docs", "adr"), ".decisions"].some((name) => present(join5(location.main, name))));
|
|
11834
|
+
const entry = lstatSync2(location.entry, { throwIfNoEntry: false });
|
|
11835
|
+
if ((present(repoData) || oldLayout) && entry && !(entry.isSymbolicLink() && existsSync3(location.entry) && realpathSync2(location.entry) === realpathSync2(repoData))) {
|
|
11836
|
+
throw new Error(`${location.entry} already exists; refusing to merge ${repoData} into it. Choose with \`acstack project-data mode\`.`);
|
|
11837
|
+
}
|
|
11838
|
+
const moved = [];
|
|
11839
|
+
if (oldLayout) moved.push(...migrateOldLayout(location.main).moved.map((path) => join5(location.main, path)));
|
|
11840
|
+
if (present(repoData) && !entry) {
|
|
11841
|
+
const tracked = git2(source, ["ls-files", "--", ACSTACK_DIR]).trim() !== "";
|
|
11842
|
+
if (tracked && source !== location.main) throw new Error(`${repoData} is committed on this worktree's branch only. Merge it into the main checkout's branch, then run migrate there.`);
|
|
11843
|
+
if (tracked) {
|
|
11844
|
+
linkTracked(location);
|
|
11845
|
+
} else {
|
|
11846
|
+
moveTree(repoData, location.entry);
|
|
11847
|
+
recordProject(location);
|
|
11848
|
+
removeManagedGitignoreBlock(source);
|
|
11849
|
+
}
|
|
11850
|
+
moved.push(location.entry);
|
|
11851
|
+
}
|
|
11852
|
+
for (const move of global2) {
|
|
11853
|
+
moveTree(move.from, move.to);
|
|
11854
|
+
moved.push(move.to);
|
|
11855
|
+
}
|
|
11856
|
+
for (const dir of new Set(global2.map((move) => dirname3(move.from)))) if (readdirSync3(dir).length === 0) rmdirSync(dir);
|
|
11857
|
+
return { moved, stranded: safeStranded(location) };
|
|
11858
|
+
}
|
|
11859
|
+
function setProjectMode(from, mode) {
|
|
11860
|
+
const location = requireLocation(from);
|
|
11861
|
+
const repoData = join5(location.main, ACSTACK_DIR);
|
|
11862
|
+
const entry = lstatSync2(location.entry, { throwIfNoEntry: false });
|
|
11863
|
+
if (mode === "tracked") {
|
|
11864
|
+
if (entry?.isSymbolicLink()) throw new Error("This project is already in tracked mode.");
|
|
11865
|
+
if (!entry) throw new Error(`No project folder at ${location.entry}.`);
|
|
11866
|
+
if (location.bare) throw new Error("A bare repository has no main checkout to hold .acstack/.");
|
|
11867
|
+
if (present(repoData)) throw new Error(`${repoData} already exists; refusing to overwrite it.`);
|
|
11868
|
+
rmSync2(join5(location.entry, PROJECT_JSON_FILE), { force: true });
|
|
11869
|
+
moveTree(location.entry, repoData);
|
|
11870
|
+
try {
|
|
11871
|
+
linkTracked(location);
|
|
11872
|
+
} catch (error) {
|
|
11873
|
+
moveTree(repoData, location.entry);
|
|
11874
|
+
recordProject(location);
|
|
11875
|
+
throw error;
|
|
11876
|
+
}
|
|
11877
|
+
} else {
|
|
11878
|
+
if (entry && !entry.isSymbolicLink()) throw new Error("This project is already in global mode.");
|
|
11879
|
+
if (!lstatSync2(repoData, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`No ${ACSTACK_DIR}/ in ${location.main} to move.`);
|
|
11880
|
+
if (entry) unlinkSync(location.entry);
|
|
11881
|
+
try {
|
|
11882
|
+
moveTree(repoData, location.entry);
|
|
11883
|
+
} catch (error) {
|
|
11884
|
+
if (entry) linkTracked(location);
|
|
11885
|
+
throw error;
|
|
11886
|
+
}
|
|
11887
|
+
recordProject(location);
|
|
11888
|
+
}
|
|
11889
|
+
return { stranded: safeStranded(location) };
|
|
11890
|
+
}
|
|
11891
|
+
function relinkProject(from, name) {
|
|
11892
|
+
const location = requireLocation(from);
|
|
11893
|
+
if (present(location.entry)) throw new Error(`${location.entry} already exists; nothing to relink.`);
|
|
11894
|
+
const orphans = findOrphans(location);
|
|
11895
|
+
const orphan = orphans.find((path) => basename2(path) === name);
|
|
11896
|
+
if (!orphan) throw new Error(orphans.length ? `${name} is not an orphaned entry for this repository. Candidates: ${orphans.map((path) => basename2(path)).join(", ")}.` : "No orphaned project entry found for this repository.");
|
|
11897
|
+
if (lstatSync2(orphan).isSymbolicLink()) {
|
|
11898
|
+
if (!hasTrackedProject(location)) throw new Error(`${orphan} is a tracked-mode link, but ${location.main} commits no ${ACSTACK_DIR}/project.yaml.`);
|
|
11899
|
+
linkTracked(location);
|
|
11900
|
+
unlinkSync(orphan);
|
|
11901
|
+
} else {
|
|
11902
|
+
renameSync2(orphan, location.entry);
|
|
11903
|
+
recordProject(location);
|
|
11904
|
+
}
|
|
11905
|
+
return { from: orphan, to: location.entry };
|
|
11906
|
+
}
|
|
11907
|
+
var import_yaml2, BEGIN, END, KNOWLEDGE;
|
|
11908
|
+
var init_project_data = __esm({
|
|
11909
|
+
"src/lib/project-data.ts"() {
|
|
11910
|
+
"use strict";
|
|
11911
|
+
import_yaml2 = __toESM(require_dist(), 1);
|
|
11912
|
+
init_atomic_file();
|
|
11913
|
+
init_project();
|
|
11914
|
+
init_constants();
|
|
11915
|
+
BEGIN = "# >>> acstack project-data >>>";
|
|
11916
|
+
END = "# <<< acstack project-data <<<";
|
|
11917
|
+
KNOWLEDGE = "VERIFICATION-KNOWLEDGE.md";
|
|
11918
|
+
}
|
|
11919
|
+
});
|
|
11920
|
+
|
|
11743
11921
|
// src/commands/project-data.ts
|
|
11744
11922
|
var project_data_exports = {};
|
|
11745
11923
|
__export(project_data_exports, {
|
|
11746
11924
|
register: () => register
|
|
11747
11925
|
});
|
|
11926
|
+
import { existsSync as existsSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
11927
|
+
function reportStranded(stranded) {
|
|
11928
|
+
for (const path of stranded) console.error(`${path} still holds its own .acstack/ changes, which ACStack no longer reads. Move what you need, then delete it.`);
|
|
11929
|
+
}
|
|
11748
11930
|
function register(program3) {
|
|
11749
|
-
const command = program3.command("project-data").description("
|
|
11750
|
-
command.command("
|
|
11931
|
+
const command = program3.command("project-data").description("Locate, migrate, and choose the mode of this repository's ACStack project data");
|
|
11932
|
+
command.command("path").description("Print the project folder for this repository, whether or not it exists yet; warn about legacy data").action(() => {
|
|
11751
11933
|
try {
|
|
11752
|
-
|
|
11934
|
+
for (const line of legacyWarnings()) console.error(`acstack: ${line}`);
|
|
11935
|
+
const location = locateProject();
|
|
11936
|
+
if (!location) throw new Error("Not in a Git repository; ACStack keys project data by repository.");
|
|
11937
|
+
for (const line of orphanWarnings(location)) console.error(`acstack: ${line}`);
|
|
11938
|
+
findProjectRoot();
|
|
11939
|
+
console.log(existsSync4(location.entry) ? realpathSync3(location.entry) : location.entry);
|
|
11753
11940
|
} catch (error) {
|
|
11754
11941
|
fail(error.message);
|
|
11755
11942
|
}
|
|
11756
11943
|
});
|
|
11757
|
-
command.command("migrate").description("
|
|
11944
|
+
command.command("migrate").description("Move legacy global files and this repository's .acstack/ into ~/.acstack; stop review servers and agents and back up first").action(() => {
|
|
11758
11945
|
try {
|
|
11759
11946
|
const result = migrateProjectData(process.cwd());
|
|
11760
11947
|
for (const path of result.moved) console.log(`Moved project data to ${path}`);
|
|
11948
|
+
reportStranded(result.stranded);
|
|
11949
|
+
} catch (error) {
|
|
11950
|
+
fail(error.message);
|
|
11951
|
+
}
|
|
11952
|
+
});
|
|
11953
|
+
command.command("mode <mode>").description("tracked: move the project folder into the main checkout's .acstack/ for committing; global: move it back").action((mode) => {
|
|
11954
|
+
try {
|
|
11955
|
+
if (mode !== "tracked" && mode !== "global") throw new Error("Mode must be tracked or global.");
|
|
11956
|
+
reportStranded(setProjectMode(process.cwd(), mode).stranded);
|
|
11957
|
+
console.log(mode === "tracked" ? "Project data is now in .acstack/ of the main checkout. Commit it to share it." : "Project data is now in the project folder. Commit the removal of .acstack/ to stop tracking it.");
|
|
11958
|
+
} catch (error) {
|
|
11959
|
+
fail(error.message);
|
|
11960
|
+
}
|
|
11961
|
+
});
|
|
11962
|
+
command.command("relink <entry>").description("Give the named project entry, left by this repository before it moved, this repository's key").action((entry) => {
|
|
11963
|
+
try {
|
|
11964
|
+
const result = relinkProject(process.cwd(), entry);
|
|
11965
|
+
console.log(`Relinked ${result.from} as ${result.to}`);
|
|
11761
11966
|
} catch (error) {
|
|
11762
11967
|
fail(error.message);
|
|
11763
11968
|
}
|
|
@@ -11777,7 +11982,7 @@ var list_exports = {};
|
|
|
11777
11982
|
__export(list_exports, {
|
|
11778
11983
|
register: () => register2
|
|
11779
11984
|
});
|
|
11780
|
-
import { existsSync as
|
|
11985
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, statSync } from "node:fs";
|
|
11781
11986
|
import { join as join6 } from "node:path";
|
|
11782
11987
|
function register2(program3) {
|
|
11783
11988
|
program3.command("list").description("List active changes or specs").option("--specs", "list specs instead of changes").option("--changes", "list changes (default)").option("--json", "output as JSON").option("--sort <order>", "sort order: name or recent", "recent").action(
|
|
@@ -11786,19 +11991,19 @@ function register2(program3) {
|
|
|
11786
11991
|
const items = [];
|
|
11787
11992
|
if (showSpecs) {
|
|
11788
11993
|
for (const name of listSpecs(root)) {
|
|
11789
|
-
const specDir = join6(root,
|
|
11994
|
+
const specDir = join6(root, SPECS_DIR, name);
|
|
11790
11995
|
const specFile = join6(specDir, "spec.md");
|
|
11791
11996
|
items.push({
|
|
11792
11997
|
name,
|
|
11793
11998
|
type: "spec",
|
|
11794
11999
|
path: specDir,
|
|
11795
|
-
modified:
|
|
12000
|
+
modified: existsSync5(specFile) ? statSync(specFile).mtime.toISOString().split("T")[0] : void 0
|
|
11796
12001
|
});
|
|
11797
12002
|
}
|
|
11798
12003
|
} else {
|
|
11799
12004
|
for (const name of listChanges(root)) {
|
|
11800
|
-
const changeDir = join6(root,
|
|
11801
|
-
const artifacts =
|
|
12005
|
+
const changeDir = join6(root, CHANGES_DIR, name);
|
|
12006
|
+
const artifacts = existsSync5(changeDir) ? readdirSync4(changeDir).filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", "")) : [];
|
|
11802
12007
|
items.push({
|
|
11803
12008
|
name,
|
|
11804
12009
|
type: "change",
|
|
@@ -11847,7 +12052,7 @@ var show_exports = {};
|
|
|
11847
12052
|
__export(show_exports, {
|
|
11848
12053
|
register: () => register3
|
|
11849
12054
|
});
|
|
11850
|
-
import { existsSync as
|
|
12055
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "node:fs";
|
|
11851
12056
|
import { join as join7 } from "node:path";
|
|
11852
12057
|
function register3(program3) {
|
|
11853
12058
|
program3.command("show").description("Display a change or spec").argument("[name]", "change or spec name").option("--json", "output as JSON").option("--type <type>", "force type: change or spec").action(
|
|
@@ -11856,17 +12061,17 @@ function register3(program3) {
|
|
|
11856
12061
|
console.error(source_default.red("Provide a change or spec name."));
|
|
11857
12062
|
process.exit(1);
|
|
11858
12063
|
}
|
|
11859
|
-
const changeDir = join7(root,
|
|
11860
|
-
const specDir = join7(root,
|
|
12064
|
+
const changeDir = join7(root, CHANGES_DIR, name);
|
|
12065
|
+
const specDir = join7(root, SPECS_DIR, name);
|
|
11861
12066
|
let type;
|
|
11862
12067
|
let targetDir;
|
|
11863
|
-
if (opts.type === "spec" || !opts.type && !
|
|
12068
|
+
if (opts.type === "spec" || !opts.type && !existsSync6(changeDir) && existsSync6(specDir)) {
|
|
11864
12069
|
type = "spec";
|
|
11865
12070
|
targetDir = specDir;
|
|
11866
|
-
} else if (
|
|
12071
|
+
} else if (existsSync6(changeDir)) {
|
|
11867
12072
|
type = "change";
|
|
11868
12073
|
targetDir = changeDir;
|
|
11869
|
-
} else if (
|
|
12074
|
+
} else if (existsSync6(specDir)) {
|
|
11870
12075
|
type = "spec";
|
|
11871
12076
|
targetDir = specDir;
|
|
11872
12077
|
} else {
|
|
@@ -11875,11 +12080,11 @@ function register3(program3) {
|
|
|
11875
12080
|
}
|
|
11876
12081
|
if (type === "spec") {
|
|
11877
12082
|
const specFile = join7(targetDir, "spec.md");
|
|
11878
|
-
if (!
|
|
12083
|
+
if (!existsSync6(specFile)) {
|
|
11879
12084
|
console.error(source_default.red(`Spec '${name}' has no spec.md file.`));
|
|
11880
12085
|
process.exit(1);
|
|
11881
12086
|
}
|
|
11882
|
-
const content =
|
|
12087
|
+
const content = readFileSync5(specFile, "utf-8");
|
|
11883
12088
|
if (opts.json) {
|
|
11884
12089
|
console.log(JSON.stringify({ name, type: "spec", content }, null, 2));
|
|
11885
12090
|
} else {
|
|
@@ -11887,10 +12092,10 @@ function register3(program3) {
|
|
|
11887
12092
|
}
|
|
11888
12093
|
return;
|
|
11889
12094
|
}
|
|
11890
|
-
const files =
|
|
12095
|
+
const files = readdirSync5(targetDir).filter((f) => f.endsWith(".md"));
|
|
11891
12096
|
const artifacts = {};
|
|
11892
12097
|
for (const file of files) {
|
|
11893
|
-
artifacts[file.replace(".md", "")] =
|
|
12098
|
+
artifacts[file.replace(".md", "")] = readFileSync5(join7(targetDir, file), "utf-8");
|
|
11894
12099
|
}
|
|
11895
12100
|
if (opts.json) {
|
|
11896
12101
|
console.log(JSON.stringify({ name, type: "change", artifacts }, null, 2));
|
|
@@ -11918,7 +12123,7 @@ var archive_exports = {};
|
|
|
11918
12123
|
__export(archive_exports, {
|
|
11919
12124
|
register: () => register4
|
|
11920
12125
|
});
|
|
11921
|
-
import { existsSync as
|
|
12126
|
+
import { existsSync as existsSync7, cpSync as cpSync2, rmSync as rmSync3 } from "node:fs";
|
|
11922
12127
|
import { join as join8 } from "node:path";
|
|
11923
12128
|
function register4(program3) {
|
|
11924
12129
|
program3.command("archive").description("Archive a completed change").argument("[name]", "change name").option("-y, --yes", "skip confirmation").option("--skip-specs", "skip living spec updates").action(
|
|
@@ -11936,19 +12141,19 @@ function register4(program3) {
|
|
|
11936
12141
|
console.log(source_default.dim("\nRun: acstack archive <name>"));
|
|
11937
12142
|
return;
|
|
11938
12143
|
}
|
|
11939
|
-
const changeDir = join8(root,
|
|
11940
|
-
if (!
|
|
12144
|
+
const changeDir = join8(root, CHANGES_DIR, name);
|
|
12145
|
+
if (!existsSync7(changeDir)) {
|
|
11941
12146
|
console.error(source_default.red(`Change '${name}' not found.`));
|
|
11942
12147
|
process.exit(1);
|
|
11943
12148
|
}
|
|
11944
12149
|
const archiveName = `${formatDate()}-${name}`;
|
|
11945
|
-
const archiveDir = join8(root,
|
|
11946
|
-
ensureDir(join8(root,
|
|
11947
|
-
|
|
11948
|
-
|
|
12150
|
+
const archiveDir = join8(root, CHANGES_DIR, ARCHIVE_DIR, archiveName);
|
|
12151
|
+
ensureDir(join8(root, CHANGES_DIR, ARCHIVE_DIR));
|
|
12152
|
+
cpSync2(changeDir, archiveDir, { recursive: true });
|
|
12153
|
+
rmSync3(changeDir, { recursive: true });
|
|
11949
12154
|
console.log(source_default.green(`Archived: ${name}`));
|
|
11950
|
-
console.log(` From: ${
|
|
11951
|
-
console.log(` To: ${
|
|
12155
|
+
console.log(` From: ${CHANGES_DIR}/${name}/`);
|
|
12156
|
+
console.log(` To: ${CHANGES_DIR}/${ARCHIVE_DIR}/${archiveName}/`);
|
|
11952
12157
|
if (!opts.skipSpecs) {
|
|
11953
12158
|
console.log(source_default.dim("\nRemember to update living specs if needed."));
|
|
11954
12159
|
}
|
|
@@ -11965,7 +12170,7 @@ var init_archive = __esm({
|
|
|
11965
12170
|
});
|
|
11966
12171
|
|
|
11967
12172
|
// src/lib/global-config.ts
|
|
11968
|
-
import { existsSync as
|
|
12173
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
11969
12174
|
import { dirname as dirname4 } from "node:path";
|
|
11970
12175
|
function parseBooleanWord(key2, raw) {
|
|
11971
12176
|
const normalized = raw.toLowerCase();
|
|
@@ -12003,8 +12208,8 @@ function assertSettableKey(key2) {
|
|
|
12003
12208
|
assertReadableKey(key2);
|
|
12004
12209
|
}
|
|
12005
12210
|
function readGlobalConfig(file) {
|
|
12006
|
-
if (!
|
|
12007
|
-
const raw =
|
|
12211
|
+
if (!existsSync8(file)) return {};
|
|
12212
|
+
const raw = readFileSync6(file, "utf-8");
|
|
12008
12213
|
try {
|
|
12009
12214
|
return JSON.parse(raw);
|
|
12010
12215
|
} catch (err) {
|
|
@@ -12012,8 +12217,8 @@ function readGlobalConfig(file) {
|
|
|
12012
12217
|
}
|
|
12013
12218
|
}
|
|
12014
12219
|
function writeGlobalConfig(config, file) {
|
|
12015
|
-
|
|
12016
|
-
|
|
12220
|
+
mkdirSync4(dirname4(file), { recursive: true });
|
|
12221
|
+
writeFileSync3(file, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
12017
12222
|
}
|
|
12018
12223
|
function parseValue(key2, raw) {
|
|
12019
12224
|
if (key2 !== "host" && !key2.startsWith("host.")) {
|
|
@@ -12318,13 +12523,13 @@ import {
|
|
|
12318
12523
|
accessSync,
|
|
12319
12524
|
chmodSync as chmodSync2,
|
|
12320
12525
|
constants as fsConstants,
|
|
12321
|
-
existsSync as
|
|
12322
|
-
readFileSync as
|
|
12323
|
-
readdirSync as
|
|
12324
|
-
realpathSync as
|
|
12526
|
+
existsSync as existsSync9,
|
|
12527
|
+
readFileSync as readFileSync7,
|
|
12528
|
+
readdirSync as readdirSync6,
|
|
12529
|
+
realpathSync as realpathSync4,
|
|
12325
12530
|
statSync as statSync2
|
|
12326
12531
|
} from "node:fs";
|
|
12327
|
-
import { basename, dirname as dirname5, join as join9 } from "node:path";
|
|
12532
|
+
import { basename as basename3, dirname as dirname5, join as join9 } from "node:path";
|
|
12328
12533
|
import { isDeepStrictEqual } from "node:util";
|
|
12329
12534
|
function projectKeySpec(key2) {
|
|
12330
12535
|
return PROJECT_KEYS[key2];
|
|
@@ -12333,8 +12538,8 @@ function profileNames() {
|
|
|
12333
12538
|
const pluginRoot = findPluginRoot();
|
|
12334
12539
|
if (!pluginRoot) return [];
|
|
12335
12540
|
const dir = join9(pluginRoot, PROFILES_DIR);
|
|
12336
|
-
if (!
|
|
12337
|
-
return
|
|
12541
|
+
if (!existsSync9(dir)) return [];
|
|
12542
|
+
return readdirSync6(dir).filter((name) => name.endsWith(".yaml")).map((name) => basename3(name, ".yaml")).sort();
|
|
12338
12543
|
}
|
|
12339
12544
|
function parseEnum(key2, raw, values) {
|
|
12340
12545
|
if (values.includes(raw)) return raw;
|
|
@@ -12451,10 +12656,10 @@ function fsReason(err) {
|
|
|
12451
12656
|
return err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
12452
12657
|
}
|
|
12453
12658
|
function readSource(file) {
|
|
12454
|
-
if (!
|
|
12659
|
+
if (!existsSync9(file)) return "";
|
|
12455
12660
|
let bytes;
|
|
12456
12661
|
try {
|
|
12457
|
-
bytes =
|
|
12662
|
+
bytes = readFileSync7(file);
|
|
12458
12663
|
} catch (err) {
|
|
12459
12664
|
throw new ConfigFileError(`Could not read ${file}: ${fsReason(err)}`);
|
|
12460
12665
|
}
|
|
@@ -12467,8 +12672,8 @@ function readSource(file) {
|
|
|
12467
12672
|
}
|
|
12468
12673
|
}
|
|
12469
12674
|
function replaceFile(file, text2) {
|
|
12470
|
-
const present2 =
|
|
12471
|
-
const target = present2 ?
|
|
12675
|
+
const present2 = existsSync9(file);
|
|
12676
|
+
const target = present2 ? realpathSync4(file) : file;
|
|
12472
12677
|
const existing = present2 ? statSync2(target) : null;
|
|
12473
12678
|
if (existing) accessSync(target, fsConstants.W_OK);
|
|
12474
12679
|
const directory = dirname5(target);
|
|
@@ -16864,7 +17069,7 @@ var init_zod = __esm({
|
|
|
16864
17069
|
});
|
|
16865
17070
|
|
|
16866
17071
|
// src/lib/nodes.ts
|
|
16867
|
-
import { existsSync as
|
|
17072
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync7 } from "node:fs";
|
|
16868
17073
|
import { join as join10 } from "node:path";
|
|
16869
17074
|
function oneOf(values) {
|
|
16870
17075
|
return `one of: ${values.join(", ")}`;
|
|
@@ -16884,7 +17089,7 @@ function bareId(reference) {
|
|
|
16884
17089
|
return reference.trim().replace(/^#/, "");
|
|
16885
17090
|
}
|
|
16886
17091
|
function mapDir(root, map) {
|
|
16887
|
-
return join10(root,
|
|
17092
|
+
return join10(root, MAPS_DIR, map);
|
|
16888
17093
|
}
|
|
16889
17094
|
function slugify(title) {
|
|
16890
17095
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "") || "node";
|
|
@@ -16960,7 +17165,7 @@ function frontmatterProblem(issue) {
|
|
|
16960
17165
|
return `${issue.path.join(".")} ${issue.message}`;
|
|
16961
17166
|
}
|
|
16962
17167
|
function parseNodeFile(dir, file) {
|
|
16963
|
-
const raw =
|
|
17168
|
+
const raw = readFileSync8(join10(dir, file), "utf-8").replace(/\r\n/g, "\n");
|
|
16964
17169
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
16965
17170
|
if (!match) {
|
|
16966
17171
|
throw new Error(`${file}: missing frontmatter`);
|
|
@@ -17004,8 +17209,8 @@ function parseNodeFile(dir, file) {
|
|
|
17004
17209
|
}
|
|
17005
17210
|
function loadNodes(root, map) {
|
|
17006
17211
|
const dir = mapDir(root, map);
|
|
17007
|
-
if (!
|
|
17008
|
-
const nodes =
|
|
17212
|
+
if (!existsSync10(dir)) return [];
|
|
17213
|
+
const nodes = readdirSync7(dir).filter((f) => /^\d+.*\.md$/.test(f)).map((f) => parseNodeFile(dir, f)).sort((a, b) => compareIds(a.id, b.id));
|
|
17009
17214
|
const seen = /* @__PURE__ */ new Map();
|
|
17010
17215
|
for (const node of nodes) {
|
|
17011
17216
|
const other = seen.get(node.id);
|
|
@@ -17978,7 +18183,7 @@ var node_exports = {};
|
|
|
17978
18183
|
__export(node_exports, {
|
|
17979
18184
|
register: () => register5
|
|
17980
18185
|
});
|
|
17981
|
-
import { existsSync as
|
|
18186
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
17982
18187
|
import { join as join11 } from "node:path";
|
|
17983
18188
|
function parseEnum2(flag, values) {
|
|
17984
18189
|
return (value) => {
|
|
@@ -18004,7 +18209,7 @@ function readBody(body, bodyFile) {
|
|
|
18004
18209
|
}
|
|
18005
18210
|
return piped;
|
|
18006
18211
|
}
|
|
18007
|
-
return
|
|
18212
|
+
return readFileSync9(bodyFile, "utf-8");
|
|
18008
18213
|
}
|
|
18009
18214
|
function refuseGithubMap(root, map, stdinRoute) {
|
|
18010
18215
|
const tracked = getProjectKey(projectFile(root), TRACKER_KEY);
|
|
@@ -18014,7 +18219,7 @@ function refuseGithubMap(root, map, stdinRoute) {
|
|
|
18014
18219
|
}
|
|
18015
18220
|
function storedNodes(root, map) {
|
|
18016
18221
|
const dir = mapDir(root, map);
|
|
18017
|
-
if (!
|
|
18222
|
+
if (!existsSync11(dir)) {
|
|
18018
18223
|
fail(`Map '${map}' does not exist - there is no ${dir} directory.`);
|
|
18019
18224
|
}
|
|
18020
18225
|
return loadNodes(root, map);
|
|
@@ -18094,7 +18299,7 @@ function registerRead(nodeCmd) {
|
|
|
18094
18299
|
if (opts.json) {
|
|
18095
18300
|
console.log(JSON.stringify({ ...toJson(node), body: node.body }, null, 2));
|
|
18096
18301
|
} else {
|
|
18097
|
-
const text2 =
|
|
18302
|
+
const text2 = readFileSync9(join11(mapDir(root, opts.map), node.file), "utf-8");
|
|
18098
18303
|
console.log(opts.visuals ? text2 : stripDiagrams(text2));
|
|
18099
18304
|
}
|
|
18100
18305
|
})
|
|
@@ -18284,12 +18489,12 @@ var init_node = __esm({
|
|
|
18284
18489
|
});
|
|
18285
18490
|
|
|
18286
18491
|
// src/lib/claude-plugins.ts
|
|
18287
|
-
import { existsSync as
|
|
18492
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
|
|
18288
18493
|
import { join as join12 } from "node:path";
|
|
18289
18494
|
function readJson(path) {
|
|
18290
|
-
if (!
|
|
18495
|
+
if (!existsSync12(path)) return void 0;
|
|
18291
18496
|
try {
|
|
18292
|
-
return JSON.parse(
|
|
18497
|
+
return JSON.parse(readFileSync10(path, "utf-8"));
|
|
18293
18498
|
} catch {
|
|
18294
18499
|
return void 0;
|
|
18295
18500
|
}
|
|
@@ -18371,7 +18576,7 @@ var init_impeccable = __esm({
|
|
|
18371
18576
|
});
|
|
18372
18577
|
|
|
18373
18578
|
// src/lib/host-probe.ts
|
|
18374
|
-
import { spawnSync } from "node:child_process";
|
|
18579
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18375
18580
|
import { accessSync as accessSync2, constants } from "node:fs";
|
|
18376
18581
|
import { request } from "node:http";
|
|
18377
18582
|
import { delimiter, join as join14 } from "node:path";
|
|
@@ -18390,7 +18595,7 @@ function firstBinaryOnPath(binaries) {
|
|
|
18390
18595
|
return binaries.find(binaryOnPath);
|
|
18391
18596
|
}
|
|
18392
18597
|
function runCommand(binary, args) {
|
|
18393
|
-
const result =
|
|
18598
|
+
const result = spawnSync3(binary, [...args], {
|
|
18394
18599
|
// In order: no standard input, capture standard output, discard standard error.
|
|
18395
18600
|
stdio: ["ignore", "pipe", "ignore"],
|
|
18396
18601
|
encoding: "utf-8",
|
|
@@ -18432,8 +18637,8 @@ var init_host_probe = __esm({
|
|
|
18432
18637
|
});
|
|
18433
18638
|
|
|
18434
18639
|
// src/commands/config-check.ts
|
|
18435
|
-
import { existsSync as
|
|
18436
|
-
import { homedir as
|
|
18640
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
|
|
18641
|
+
import { homedir as homedir3 } from "node:os";
|
|
18437
18642
|
import { join as join15 } from "node:path";
|
|
18438
18643
|
function checkRequiredAxes(report, config, project) {
|
|
18439
18644
|
report.heading("Required host axes are set");
|
|
@@ -18584,9 +18789,9 @@ function firstLineOf(err) {
|
|
|
18584
18789
|
return (err instanceof Error ? err.message : String(err)).split("\n")[0];
|
|
18585
18790
|
}
|
|
18586
18791
|
function readJsonFile(path) {
|
|
18587
|
-
if (!
|
|
18792
|
+
if (!existsSync13(path)) return { status: "missing" };
|
|
18588
18793
|
try {
|
|
18589
|
-
return { status: "read", value: JSON.parse(
|
|
18794
|
+
return { status: "read", value: JSON.parse(readFileSync11(path, "utf-8")) };
|
|
18590
18795
|
} catch (err) {
|
|
18591
18796
|
return { status: "unreadable", detail: firstLineOf(err) };
|
|
18592
18797
|
}
|
|
@@ -18602,7 +18807,7 @@ function checkDomainMap(report, root, specSync) {
|
|
|
18602
18807
|
const id = "domain-map";
|
|
18603
18808
|
const consequence = "spec sync then skips silently and the living specs stop being updated";
|
|
18604
18809
|
const path = domainMapFile(root);
|
|
18605
|
-
if (!
|
|
18810
|
+
if (!existsSync13(path)) {
|
|
18606
18811
|
report.line(
|
|
18607
18812
|
specSync ? "fail" : "info",
|
|
18608
18813
|
id,
|
|
@@ -18674,8 +18879,8 @@ function checkAgentBrowserJson(report, root, expected) {
|
|
|
18674
18879
|
}
|
|
18675
18880
|
function checkVerificationKnowledge(report, root) {
|
|
18676
18881
|
const id = "verification-knowledge";
|
|
18677
|
-
const relative7 = VERIFICATION_KNOWLEDGE_FILE;
|
|
18678
|
-
if (!isFile(
|
|
18882
|
+
const relative7 = join15(root, VERIFICATION_KNOWLEDGE_FILE);
|
|
18883
|
+
if (!isFile(relative7)) {
|
|
18679
18884
|
report.line(
|
|
18680
18885
|
"fail",
|
|
18681
18886
|
id,
|
|
@@ -18741,7 +18946,7 @@ function settingsFiles(root) {
|
|
|
18741
18946
|
},
|
|
18742
18947
|
{
|
|
18743
18948
|
label: join15("~", CLAUDE_DIR, CLAUDE_SETTINGS_FILE),
|
|
18744
|
-
path: join15(
|
|
18949
|
+
path: join15(homedir3(), CLAUDE_DIR, CLAUDE_SETTINGS_FILE)
|
|
18745
18950
|
}
|
|
18746
18951
|
];
|
|
18747
18952
|
}
|
|
@@ -18930,7 +19135,7 @@ __export(config_exports, {
|
|
|
18930
19135
|
register: () => register6
|
|
18931
19136
|
});
|
|
18932
19137
|
function reportUserError(err) {
|
|
18933
|
-
if (err instanceof ConfigValidationError || err instanceof ConfigFileError) {
|
|
19138
|
+
if (err instanceof ConfigValidationError || err instanceof ConfigFileError || err instanceof ProjectDataError) {
|
|
18934
19139
|
console.error(source_default.red(err.message));
|
|
18935
19140
|
process.exit(1);
|
|
18936
19141
|
}
|
|
@@ -18971,7 +19176,6 @@ function loadProject() {
|
|
|
18971
19176
|
workflow: projectWorkflow(void 0)
|
|
18972
19177
|
};
|
|
18973
19178
|
}
|
|
18974
|
-
assertCanonicalProject(root);
|
|
18975
19179
|
const yaml = readYaml(projectFile(root));
|
|
18976
19180
|
return {
|
|
18977
19181
|
root,
|
|
@@ -19047,7 +19251,6 @@ function projectFileFor(key2, use) {
|
|
|
19047
19251
|
);
|
|
19048
19252
|
}
|
|
19049
19253
|
}
|
|
19050
|
-
assertCanonicalProject(root);
|
|
19051
19254
|
return projectFile(root);
|
|
19052
19255
|
}
|
|
19053
19256
|
function setProjectFileKey(key2, value) {
|
|
@@ -19114,7 +19317,6 @@ function printProjectKeys(root) {
|
|
|
19114
19317
|
function listConfig(asJson) {
|
|
19115
19318
|
const config = readGlobalConfig(GLOBAL_CONFIG_FILE);
|
|
19116
19319
|
const root = findProjectRoot();
|
|
19117
|
-
if (root) assertCanonicalProject(root);
|
|
19118
19320
|
if (asJson) {
|
|
19119
19321
|
const project = root ? listProjectKeys(projectFile(root)) : null;
|
|
19120
19322
|
console.log(JSON.stringify({ ...config, project }, null, 2));
|
|
@@ -19165,7 +19367,7 @@ async function checkConfig(asJson) {
|
|
|
19165
19367
|
checkOptionalAxes(report, config);
|
|
19166
19368
|
checkProjectFiles(report, loaded);
|
|
19167
19369
|
checkImpeccable(report);
|
|
19168
|
-
checkOutputStyle(report,
|
|
19370
|
+
checkOutputStyle(report, checkoutRoot() ?? process.cwd());
|
|
19169
19371
|
report.finish(asJson);
|
|
19170
19372
|
}
|
|
19171
19373
|
function reportBrowserMode(asJson) {
|
|
@@ -19241,7 +19443,6 @@ var UNKNOWN_KEY_MARK;
|
|
|
19241
19443
|
var init_config = __esm({
|
|
19242
19444
|
"src/commands/config.ts"() {
|
|
19243
19445
|
"use strict";
|
|
19244
|
-
init_project_data();
|
|
19245
19446
|
init_source();
|
|
19246
19447
|
init_constants();
|
|
19247
19448
|
init_global_config();
|
|
@@ -19380,25 +19581,25 @@ var init_alignment_packs = __esm({
|
|
|
19380
19581
|
});
|
|
19381
19582
|
|
|
19382
19583
|
// src/lib/design-doc.ts
|
|
19383
|
-
import { readFileSync as
|
|
19584
|
+
import { readFileSync as readFileSync12, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
19384
19585
|
import { dirname as dirname6, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "node:path";
|
|
19385
|
-
import { createHash } from "node:crypto";
|
|
19586
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
19386
19587
|
function readBounded(file) {
|
|
19387
19588
|
const stat = statSync4(file);
|
|
19388
19589
|
if (!stat.isFile() || stat.size > 262144) throw new Error(`${file}: file type or size exceeds the 256 KiB limit`);
|
|
19389
|
-
const bytes =
|
|
19590
|
+
const bytes = readFileSync12(file);
|
|
19390
19591
|
if (bytes.length > 262144) throw new Error(`${file}: file size exceeds the 256 KiB limit`);
|
|
19391
19592
|
return bytes;
|
|
19392
19593
|
}
|
|
19393
19594
|
function childPath(root, name) {
|
|
19394
19595
|
if (isAbsolute2(name) || name.split(/[\\/]/).includes("..")) throw new Error(`Invalid design path: ${name}`);
|
|
19395
|
-
const path =
|
|
19596
|
+
const path = realpathSync5(resolve3(root, name));
|
|
19396
19597
|
const rel = relative2(root, path);
|
|
19397
19598
|
if (rel.split(/[\\/]/)[0] === ".." || isAbsolute2(rel)) throw new Error(`Design path escapes source directory: ${name}`);
|
|
19398
19599
|
return path;
|
|
19399
19600
|
}
|
|
19400
19601
|
function loadDesignDoc(entryPath) {
|
|
19401
|
-
const path =
|
|
19602
|
+
const path = realpathSync5(entryPath);
|
|
19402
19603
|
const root = dirname6(path);
|
|
19403
19604
|
const captured = /* @__PURE__ */ new Map();
|
|
19404
19605
|
let totalBytes = 0;
|
|
@@ -19427,7 +19628,7 @@ function loadDesignDoc(entryPath) {
|
|
|
19427
19628
|
return { ...element, file, detailText: readDetail(element.detail) };
|
|
19428
19629
|
});
|
|
19429
19630
|
const detailText = readDetail(entry.detail);
|
|
19430
|
-
const sourceRevision =
|
|
19631
|
+
const sourceRevision = createHash2("sha256").update(JSON.stringify([...captured].sort(([a], [b]) => a.localeCompare(b)).map(([name, bytes]) => [name, bytes.toString("base64")]))).digest("hex");
|
|
19431
19632
|
const doc = { ...entry, items, detailText, sourceRevision };
|
|
19432
19633
|
const ids = /* @__PURE__ */ new Set([entry.id]);
|
|
19433
19634
|
for (const item of items) {
|
|
@@ -19499,7 +19700,7 @@ function designDocToPack(doc, orientation = "top-to-bottom") {
|
|
|
19499
19700
|
const edges = [...new Map(doc.items.flatMap((item) => item.relationships).map((edge) => [edge.id, edge])).values()];
|
|
19500
19701
|
const content = { ...doc };
|
|
19501
19702
|
delete content.sourceRevision;
|
|
19502
|
-
const version =
|
|
19703
|
+
const version = createHash2("sha256").update(JSON.stringify(content)).digest("hex").slice(0, 16);
|
|
19503
19704
|
return AlignmentPackSchema.parse({
|
|
19504
19705
|
documentKind: "design",
|
|
19505
19706
|
packVersion: `${doc.id}@${version}`,
|
|
@@ -19666,21 +19867,21 @@ var init_alignment_pack_renderer = __esm({
|
|
|
19666
19867
|
|
|
19667
19868
|
// src/lib/design-map.ts
|
|
19668
19869
|
import { execFileSync } from "node:child_process";
|
|
19669
|
-
import { realpathSync as
|
|
19870
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
19670
19871
|
import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve4, win32 } from "node:path";
|
|
19671
|
-
import { createHash as
|
|
19872
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
19672
19873
|
function designMapInputs(root, doc, entry) {
|
|
19673
19874
|
let nodes = [];
|
|
19674
19875
|
if (doc.planning) {
|
|
19675
19876
|
const { tracker, map } = doc.planning;
|
|
19676
19877
|
if (tracker === "files") {
|
|
19677
|
-
const base =
|
|
19678
|
-
const path =
|
|
19679
|
-
const
|
|
19680
|
-
if (!
|
|
19878
|
+
const base = realpathSync6(mapDir(root, "."));
|
|
19879
|
+
const path = realpathSync6(mapDir(root, map));
|
|
19880
|
+
const rel = relative3(base, path);
|
|
19881
|
+
if (!rel || isAbsolute3(rel) || rel.split(/[\\/]/).includes("..")) throw new Error("Invalid bound map path");
|
|
19681
19882
|
nodes = loadNodes(root, map);
|
|
19682
19883
|
} else {
|
|
19683
|
-
const raw = execFileSync("gh", ["issue", "list", "--state", "all", "--limit", "1000", "--json", "number,title,body,state,stateReason,labels,url"], {
|
|
19884
|
+
const raw = execFileSync("gh", ["issue", "list", "--state", "all", "--limit", "1000", "--json", "number,title,body,state,stateReason,labels,url"], { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
|
|
19684
19885
|
try {
|
|
19685
19886
|
const issues = JSON.parse(raw);
|
|
19686
19887
|
if (Array.isArray(issues) && issues.length >= 1e3) throw new Error("GitHub issue list may be truncated at the 1000-issue limit; map completeness cannot be verified");
|
|
@@ -19697,10 +19898,18 @@ function designMapInputs(root, doc, entry) {
|
|
|
19697
19898
|
if (references.length !== 1) throw new Error("Bound map root needs exactly one Design entry reference");
|
|
19698
19899
|
const reference = references[0][1].trim();
|
|
19699
19900
|
if (!reference || isAbsolute3(reference) || win32.isAbsolute(reference)) throw new Error("Invalid bound map Design entry path");
|
|
19700
|
-
const
|
|
19701
|
-
const
|
|
19702
|
-
|
|
19703
|
-
|
|
19901
|
+
const bases = [root, checkoutRoot()].filter((base) => base !== null).map((base) => realpathSync6(base));
|
|
19902
|
+
const within = bases.some((base) => {
|
|
19903
|
+
let target;
|
|
19904
|
+
try {
|
|
19905
|
+
target = realpathSync6(resolve4(base, reference));
|
|
19906
|
+
} catch {
|
|
19907
|
+
return false;
|
|
19908
|
+
}
|
|
19909
|
+
const rel = relative3(base, target);
|
|
19910
|
+
return Boolean(rel) && !isAbsolute3(rel) && !rel.split(/[\\/]/).includes("..") && target === realpathSync6(entry);
|
|
19911
|
+
});
|
|
19912
|
+
if (!within) throw new Error("Bound map Design entry does not match the authored entry");
|
|
19704
19913
|
}
|
|
19705
19914
|
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
19706
19915
|
const targets = /* @__PURE__ */ new Set([doc.id, ...doc.items.map((item) => item.id), ...doc.items.flatMap((item) => item.relationships.map((edge) => edge.id)), ...doc.concerns.map((concern) => concern.id)]);
|
|
@@ -19738,7 +19947,8 @@ var init_design_map = __esm({
|
|
|
19738
19947
|
init_zod();
|
|
19739
19948
|
init_nodes();
|
|
19740
19949
|
init_github_issues();
|
|
19741
|
-
|
|
19950
|
+
init_project();
|
|
19951
|
+
digest = (value) => createHash3("sha256").update(JSON.stringify(value)).digest("hex");
|
|
19742
19952
|
ContributionSchema = external_exports.object({
|
|
19743
19953
|
id: external_exports.string().min(1),
|
|
19744
19954
|
designId: external_exports.string().min(1),
|
|
@@ -19845,28 +20055,28 @@ var init_review_server = __esm({
|
|
|
19845
20055
|
|
|
19846
20056
|
// src/lib/design-review.ts
|
|
19847
20057
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
19848
|
-
import { existsSync as
|
|
19849
|
-
import { join as join16, resolve as resolve5, dirname as dirname7, basename as
|
|
20058
|
+
import { existsSync as existsSync14, lstatSync as lstatSync3, mkdirSync as mkdirSync5, opendirSync, readFileSync as readFileSync13, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
20059
|
+
import { join as join16, resolve as resolve5, dirname as dirname7, basename as basename4 } from "node:path";
|
|
19850
20060
|
function readDesignReviewHistory(root, identity2, path) {
|
|
19851
20061
|
const canonical = resolve5(path);
|
|
19852
|
-
const base = join16(root,
|
|
19853
|
-
if (
|
|
19854
|
-
for (const directory of [
|
|
20062
|
+
const base = join16(root, "design-reviews", identity2);
|
|
20063
|
+
if (basename4(canonical) !== "review.json" || !/^[a-f0-9]{16}$/.test(basename4(dirname7(canonical))) || dirname7(dirname7(canonical)) !== base) throw new Error("Not canonical history for this design");
|
|
20064
|
+
for (const directory of [root, join16(root, "design-reviews"), base, dirname7(canonical)]) {
|
|
19855
20065
|
if (lstatSync3(directory).isSymbolicLink() || !lstatSync3(directory).isDirectory()) throw new Error("Invalid review history directory");
|
|
19856
20066
|
}
|
|
19857
20067
|
const stat = lstatSync3(canonical);
|
|
19858
20068
|
if (!stat.isFile() || stat.size > 1e6) throw new Error("Invalid review history file");
|
|
19859
20069
|
let snapshot;
|
|
19860
20070
|
try {
|
|
19861
|
-
snapshot = StoredReviewSchema.parse(JSON.parse(
|
|
20071
|
+
snapshot = StoredReviewSchema.parse(JSON.parse(readFileSync13(canonical, "utf8")));
|
|
19862
20072
|
} catch (error) {
|
|
19863
20073
|
throw new Error(`Cannot read design review history ${canonical}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
19864
20074
|
}
|
|
19865
|
-
if (snapshot.packVersion !== `${identity2}@${
|
|
20075
|
+
if (snapshot.packVersion !== `${identity2}@${basename4(dirname7(canonical))}` || snapshot.annotations.some((annotation) => annotation.packVersion !== snapshot.packVersion)) throw new Error("Review history identity mismatch");
|
|
19866
20076
|
return snapshot;
|
|
19867
20077
|
}
|
|
19868
20078
|
function readDesignReviewHistories(root, identity2) {
|
|
19869
|
-
const base = join16(root,
|
|
20079
|
+
const base = join16(root, "design-reviews", identity2);
|
|
19870
20080
|
const histories = /* @__PURE__ */ new Map();
|
|
19871
20081
|
const directory = opendirSync(base);
|
|
19872
20082
|
let entries = 0;
|
|
@@ -19889,11 +20099,11 @@ function atomicWrite(path, text2) {
|
|
|
19889
20099
|
const temporary = `${path}.${randomBytes3(16).toString("hex")}.tmp`;
|
|
19890
20100
|
let created = false;
|
|
19891
20101
|
try {
|
|
19892
|
-
|
|
20102
|
+
writeFileSync4(temporary, text2, { flag: "wx", mode: 384 });
|
|
19893
20103
|
created = true;
|
|
19894
20104
|
renameSync3(temporary, path);
|
|
19895
20105
|
} finally {
|
|
19896
|
-
if (created)
|
|
20106
|
+
if (created) rmSync4(temporary, { force: true });
|
|
19897
20107
|
}
|
|
19898
20108
|
}
|
|
19899
20109
|
function openDesignReview(root, pack, initialOperation) {
|
|
@@ -19901,12 +20111,11 @@ function openDesignReview(root, pack, initialOperation) {
|
|
|
19901
20111
|
const version = pack.source.map.version;
|
|
19902
20112
|
if (!identity2 || !/^[a-zA-Z][a-zA-Z0-9_-]{0,79}$/.test(identity2) || !/^[a-f0-9]{16}$/.test(version)) throw new Error("Invalid design review identity");
|
|
19903
20113
|
let directory = root;
|
|
19904
|
-
for (const part of [
|
|
20114
|
+
for (const part of ["design-reviews", identity2, version]) {
|
|
19905
20115
|
directory = join16(directory, part);
|
|
19906
|
-
if (!
|
|
20116
|
+
if (!existsSync14(directory)) mkdirSync5(directory);
|
|
19907
20117
|
if (lstatSync3(directory).isSymbolicLink() || !lstatSync3(directory).isDirectory()) throw new Error("Design review directory must be a real directory");
|
|
19908
20118
|
}
|
|
19909
|
-
if (!initialOperation) ensureProjectGitignore(root);
|
|
19910
20119
|
const canonical = join16(directory, "review.json");
|
|
19911
20120
|
const validate = (snapshot2) => {
|
|
19912
20121
|
if (snapshot2.packVersion !== pack.packVersion) throw new Error("Design review pack version mismatch");
|
|
@@ -19919,20 +20128,20 @@ function openDesignReview(root, pack, initialOperation) {
|
|
|
19919
20128
|
const lock = join16(directory, ".writer-lock");
|
|
19920
20129
|
const acquire = () => {
|
|
19921
20130
|
try {
|
|
19922
|
-
|
|
20131
|
+
mkdirSync5(lock);
|
|
19923
20132
|
} catch (error) {
|
|
19924
20133
|
if (error.code === "EEXIST") throw new ReviewHttpError(409, `Another writer holds ${lock}. If a writer crashed, remove that directory before retrying.`);
|
|
19925
20134
|
throw error;
|
|
19926
20135
|
}
|
|
19927
20136
|
};
|
|
19928
20137
|
const release = () => {
|
|
19929
|
-
|
|
20138
|
+
rmSync4(lock, { recursive: true });
|
|
19930
20139
|
};
|
|
19931
20140
|
const readSnapshot = () => {
|
|
19932
20141
|
try {
|
|
19933
20142
|
const stat = lstatSync3(canonical);
|
|
19934
20143
|
if (!stat.isFile() || stat.size > 1e6) throw new Error("Invalid review file");
|
|
19935
|
-
const saved = StoredReviewSchema.parse(JSON.parse(
|
|
20144
|
+
const saved = StoredReviewSchema.parse(JSON.parse(readFileSync13(canonical, "utf8")));
|
|
19936
20145
|
validate(saved);
|
|
19937
20146
|
return saved;
|
|
19938
20147
|
} catch (error) {
|
|
@@ -20007,8 +20216,6 @@ var init_design_review = __esm({
|
|
|
20007
20216
|
"use strict";
|
|
20008
20217
|
init_zod();
|
|
20009
20218
|
init_alignment_packs();
|
|
20010
|
-
init_constants();
|
|
20011
|
-
init_project_data();
|
|
20012
20219
|
init_review_server();
|
|
20013
20220
|
DesignReviewSnapshotSchema = external_exports.object({
|
|
20014
20221
|
packVersion: external_exports.string().min(1),
|
|
@@ -20068,12 +20275,12 @@ function openDesignLifecycle(root, entry, reviewFile) {
|
|
|
20068
20275
|
const staleRendered = doc.sourceRevision !== rendered.sourceRevision;
|
|
20069
20276
|
const inputs = designMapInputs(root, doc, entry);
|
|
20070
20277
|
const feedbackPath = reviewFile ? resolve6(reviewFile) : review.reviewPath;
|
|
20071
|
-
const latestReceipts = new Map(saved.lifecycle?.reconciliations.map((receipt) => [
|
|
20278
|
+
const latestReceipts = new Map(saved.lifecycle?.reconciliations.map((receipt) => [historyPath(root, receipt.reviewPath), receipt]));
|
|
20072
20279
|
const histories = readDesignReviewHistories(root, doc.id);
|
|
20073
20280
|
histories.set(review.reviewPath, saved);
|
|
20074
20281
|
const obligations = /* @__PURE__ */ new Set();
|
|
20075
20282
|
for (const [path, snapshot] of histories) {
|
|
20076
|
-
const receipts = new Map(snapshot.lifecycle?.reconciliations.map((receipt) => [
|
|
20283
|
+
const receipts = new Map(snapshot.lifecycle?.reconciliations.map((receipt) => [historyPath(root, receipt.reviewPath), receipt]));
|
|
20077
20284
|
for (const [selected, receipt] of receipts) {
|
|
20078
20285
|
if (selected !== path || snapshot.annotations.length || receipt.decisions.some((decision) => decision.outcome === "needs-human")) obligations.add(selected);
|
|
20079
20286
|
}
|
|
@@ -20086,24 +20293,21 @@ function openDesignLifecycle(root, entry, reviewFile) {
|
|
|
20086
20293
|
return { path, snapshot, receipt, fresh };
|
|
20087
20294
|
});
|
|
20088
20295
|
const feedback = scopes.find((scope) => scope.path === feedbackPath).snapshot;
|
|
20089
|
-
const
|
|
20090
|
-
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20097
|
-
annotations: snapshot.annotations
|
|
20098
|
-
})))
|
|
20099
|
-
};
|
|
20296
|
+
const commentsDigest = (prefix) => digest(scopes.map(({ path, snapshot }) => ({
|
|
20297
|
+
path: prefix + relative4(root, path).split(sep).join("/"),
|
|
20298
|
+
packVersion: snapshot.packVersion,
|
|
20299
|
+
revision: snapshot.revision,
|
|
20300
|
+
annotations: snapshot.annotations
|
|
20301
|
+
})));
|
|
20302
|
+
const tuple = { sourceRevision: doc.sourceRevision, reviewRevision: feedback.revision, mapInputsDigest: inputs.mapInputsDigest, commentsDigest: commentsDigest("") };
|
|
20303
|
+
const legacyTuple = { ...tuple, commentsDigest: commentsDigest(LEGACY_PREFIX) };
|
|
20100
20304
|
const unsettled = scopes.find((scope) => (obligations.has(scope.path) || scope.receipt || scope.snapshot.annotations.length) && (!scope.fresh || scope.receipt?.decisions.some((decision) => decision.outcome === "needs-human")));
|
|
20101
20305
|
let reason;
|
|
20102
20306
|
if (staleRendered) reason = "Authored source changed. Reopen this revision before reviewing.";
|
|
20103
20307
|
else if (inputs.blockingQuestions.length) reason = "Unresolved design decisions";
|
|
20104
20308
|
else if (unsettled) reason = unsettled.fresh ? "Reconciliation needs a human decision" : "Feedback and contributions need explicit reconciliation";
|
|
20105
20309
|
else if (inputs.contributions.length && !scopes.some((scope) => scope.fresh)) reason = "Feedback and contributions need explicit reconciliation";
|
|
20106
|
-
const approved = saved.lifecycle?.approvals.some((item) => sameTuple(item, tuple)) ?? false;
|
|
20310
|
+
const approved = saved.lifecycle?.approvals.some((item) => sameTuple(item, tuple) || sameTuple(item, legacyTuple)) ?? false;
|
|
20107
20311
|
return {
|
|
20108
20312
|
...tuple,
|
|
20109
20313
|
planning: doc.planning ?? null,
|
|
@@ -20174,9 +20378,13 @@ function openDesignLifecycle(root, entry, reviewFile) {
|
|
|
20174
20378
|
})
|
|
20175
20379
|
};
|
|
20176
20380
|
}
|
|
20381
|
+
function historyPath(root, reviewPath) {
|
|
20382
|
+
return resolve6(root, reviewPath.startsWith(LEGACY_PREFIX) ? reviewPath.slice(LEGACY_PREFIX.length) : reviewPath);
|
|
20383
|
+
}
|
|
20177
20384
|
function sameTuple(a, b) {
|
|
20178
20385
|
return a.sourceRevision === b.sourceRevision && a.reviewRevision === b.reviewRevision && a.commentsDigest === b.commentsDigest && a.mapInputsDigest === b.mapInputsDigest;
|
|
20179
20386
|
}
|
|
20387
|
+
var LEGACY_PREFIX;
|
|
20180
20388
|
var init_design_lifecycle = __esm({
|
|
20181
20389
|
"src/lib/design-lifecycle.ts"() {
|
|
20182
20390
|
"use strict";
|
|
@@ -20185,12 +20393,13 @@ var init_design_lifecycle = __esm({
|
|
|
20185
20393
|
init_design_map();
|
|
20186
20394
|
init_design_review();
|
|
20187
20395
|
init_review_server();
|
|
20396
|
+
LEGACY_PREFIX = ".acstack/";
|
|
20188
20397
|
}
|
|
20189
20398
|
});
|
|
20190
20399
|
|
|
20191
20400
|
// src/lib/design-migration.ts
|
|
20192
20401
|
import { constants as constants2, closeSync, fstatSync, lstatSync as lstatSync4, openSync, opendirSync as opendirSync2, readSync as readSync2 } from "node:fs";
|
|
20193
|
-
import { basename as
|
|
20402
|
+
import { basename as basename5, dirname as dirname8, isAbsolute as isAbsolute4, join as join17, relative as relative5, resolve as resolve7 } from "node:path";
|
|
20194
20403
|
import { TextDecoder as TextDecoder2 } from "node:util";
|
|
20195
20404
|
function noLinks(path) {
|
|
20196
20405
|
for (let cursor = resolve7(path); ; cursor = dirname8(cursor)) {
|
|
@@ -20249,25 +20458,12 @@ function legacyComments(annotations) {
|
|
|
20249
20458
|
count: Math.max(1, group.ids.size + [...group.anonymousAnchors].filter((anchor) => !group.identifiedAnchors.has(anchor)).length)
|
|
20250
20459
|
}]));
|
|
20251
20460
|
}
|
|
20252
|
-
function mainCheckout(root) {
|
|
20253
|
-
let git = join17(root, ".git");
|
|
20254
|
-
noLinks(git);
|
|
20255
|
-
if (!lstatSync4(git).isDirectory()) {
|
|
20256
|
-
const pointer = readBounded2(git, 4096).trim().match(/^gitdir: (.+)$/);
|
|
20257
|
-
if (!pointer) throw new Error("Invalid legacy decision-log Git directory");
|
|
20258
|
-
git = resolve7(root, pointer[1]);
|
|
20259
|
-
noLinks(git);
|
|
20260
|
-
}
|
|
20261
|
-
if (lstatSync4(join17(git, "commondir"), { throwIfNoEntry: false })) git = resolve7(git, readBounded2(join17(git, "commondir"), 4096).trim());
|
|
20262
|
-
noLinks(git);
|
|
20263
|
-
return dirname8(git);
|
|
20264
|
-
}
|
|
20265
20461
|
function migrateDesignReview(root, entry, options) {
|
|
20266
20462
|
const pack = designDocToPack(loadDesignDoc(entry));
|
|
20267
20463
|
const directory = resolve7(options.legacyDir);
|
|
20268
|
-
const base = join17(root, "
|
|
20464
|
+
const base = join17(root, "alignment-packs");
|
|
20269
20465
|
const rel = relative5(base, directory);
|
|
20270
|
-
if (!rel || isAbsolute4(rel) || rel.split(/[\\/]/).includes("..")) throw new Error("Legacy directory must be under
|
|
20466
|
+
if (!rel || isAbsolute4(rel) || rel.split(/[\\/]/).includes("..")) throw new Error("Legacy directory must be under alignment-packs in the project folder");
|
|
20271
20467
|
noLinks(directory);
|
|
20272
20468
|
if (!lstatSync4(directory).isDirectory()) throw new Error("Invalid legacy directory");
|
|
20273
20469
|
const sources = [];
|
|
@@ -20304,7 +20500,7 @@ function migrateDesignReview(root, entry, options) {
|
|
|
20304
20500
|
const scripts = [...content.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi)].filter((match) => new RegExp(`\\bid=["']${PACK_DATA_ID}["']`).test(match[1]));
|
|
20305
20501
|
if (scripts.length !== 1 || scripts[0][1].trim() !== `id="${PACK_DATA_ID}" type="application/json"`) throw new Error(`Invalid legacy pack JSON metadata: ${name}`);
|
|
20306
20502
|
const saved = parseJson(`Legacy pack ${path}`, scripts[0][2], AlignmentPackSchema);
|
|
20307
|
-
if (saved.source.map.id !==
|
|
20503
|
+
if (saved.source.map.id !== basename5(directory) || saved.packVersion !== `${saved.source.map.id}@${saved.source.map.version}`) throw new Error("Legacy pack directory identity mismatch");
|
|
20308
20504
|
if (saved.reviewState.approval && saved.reviewState.approval.packVersion !== saved.packVersion) throw new Error("Legacy pack approval version mismatch");
|
|
20309
20505
|
const previous = versions.get(saved.packVersion);
|
|
20310
20506
|
if (previous && digest(previous) !== digest(saved)) throw new Error("Conflicting duplicate legacy pack version");
|
|
@@ -20336,13 +20532,11 @@ function migrateDesignReview(root, entry, options) {
|
|
|
20336
20532
|
ids.set(identity2, fingerprint2);
|
|
20337
20533
|
}
|
|
20338
20534
|
}
|
|
20339
|
-
const
|
|
20340
|
-
|
|
20341
|
-
const decisionPath = join17(main, ".acstack", "decision-logs", `${basename3(directory)}.tsv`);
|
|
20342
|
-
for (const path of [join17(main, ".acstack"), dirname8(decisionPath), decisionPath]) {
|
|
20535
|
+
const decisionPath = join17(root, "decision-logs", `${basename5(directory)}.tsv`);
|
|
20536
|
+
for (const path of [dirname8(decisionPath), decisionPath]) {
|
|
20343
20537
|
if (lstatSync4(path, { throwIfNoEntry: false })) noLinks(path);
|
|
20344
20538
|
}
|
|
20345
|
-
const decisionLog = lstatSync4(decisionPath, { throwIfNoEntry: false }) ? { path:
|
|
20539
|
+
const decisionLog = lstatSync4(decisionPath, { throwIfNoEntry: false }) ? { path: `decision-logs/${basename5(directory)}.tsv`, content: readBounded2(decisionPath, FILE_LIMIT) } : void 0;
|
|
20346
20540
|
const history = { directory: relative5(root, directory).split("\\").join("/"), sources: sources.sort((a, b) => a.path.localeCompare(b.path)), packs: [...versions.values()], annotations, mapping, feedback, decisionLog };
|
|
20347
20541
|
const fingerprint = digest(history);
|
|
20348
20542
|
const apply = (snapshot) => {
|
|
@@ -20373,7 +20567,6 @@ var init_design_migration = __esm({
|
|
|
20373
20567
|
init_design_doc();
|
|
20374
20568
|
init_design_map();
|
|
20375
20569
|
init_design_review();
|
|
20376
|
-
init_project_data();
|
|
20377
20570
|
FILE_LIMIT = 1e6;
|
|
20378
20571
|
HTML_LIMIT = 8e6;
|
|
20379
20572
|
TOTAL_LIMIT = 16e6;
|
|
@@ -20387,13 +20580,13 @@ __export(design_doc_exports, {
|
|
|
20387
20580
|
generateDesignDoc: () => generateDesignDoc,
|
|
20388
20581
|
register: () => register7
|
|
20389
20582
|
});
|
|
20390
|
-
import { readFileSync as
|
|
20583
|
+
import { readFileSync as readFileSync14, statSync as statSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
20391
20584
|
import { resolve as resolve8 } from "node:path";
|
|
20392
20585
|
function generateDesignDoc(entry, output, markdown, orientation = "top-to-bottom") {
|
|
20393
20586
|
const doc = loadDesignDoc(entry);
|
|
20394
20587
|
const html = renderAlignmentPackHtml(designDocToPack(doc, orientation));
|
|
20395
|
-
|
|
20396
|
-
if (markdown)
|
|
20588
|
+
writeFileSync5(resolve8(output), html, { flag: "wx" });
|
|
20589
|
+
if (markdown) writeFileSync5(resolve8(markdown), renderDesignDocMarkdown(doc, orientation), { flag: "wx" });
|
|
20397
20590
|
}
|
|
20398
20591
|
function register7(program3) {
|
|
20399
20592
|
const lifecycle = program3.command("design-review").description("Inspect and reconcile exact authored design revisions");
|
|
@@ -20407,7 +20600,7 @@ function register7(program3) {
|
|
|
20407
20600
|
if (!statSync5(options.input).isFile() || statSync5(options.input).size > 1e6) throw new Error("Invalid reconciliation input");
|
|
20408
20601
|
let input;
|
|
20409
20602
|
try {
|
|
20410
|
-
input = JSON.parse(
|
|
20603
|
+
input = JSON.parse(readFileSync14(options.input, "utf8"));
|
|
20411
20604
|
} catch (error) {
|
|
20412
20605
|
throw new Error(`Invalid reconciliation input ${options.input}: ${error.message}`);
|
|
20413
20606
|
}
|
|
@@ -20419,7 +20612,7 @@ function register7(program3) {
|
|
|
20419
20612
|
const lifecycle2 = openDesignLifecycle(root, entry);
|
|
20420
20613
|
const doc = lifecycle2.doc;
|
|
20421
20614
|
const pack = designDocToPack(doc, options.orientation);
|
|
20422
|
-
if (options.markdown)
|
|
20615
|
+
if (options.markdown) writeFileSync5(resolve8(options.markdown), renderDesignDocMarkdown(doc, options.orientation), { flag: "wx" });
|
|
20423
20616
|
lifecycle2.status();
|
|
20424
20617
|
const review = lifecycle2.review;
|
|
20425
20618
|
const handoffPrompt = `Read the review JSON at ${JSON.stringify(review.reviewPath)}. Resolve feedback against the authored design entry ${JSON.stringify(resolve8(entry))} and update the authored source to address the comments. Explain unresolved decisions, run design-review status, explicitly reconcile the selected feedback and map contributions with design-review reconcile, and reopen the review with design-doc for approval of the resulting revision.`;
|
|
@@ -20472,7 +20665,12 @@ function impeccableJson(impeccable) {
|
|
|
20472
20665
|
};
|
|
20473
20666
|
}
|
|
20474
20667
|
function runDesignGate(json) {
|
|
20475
|
-
|
|
20668
|
+
let root = null;
|
|
20669
|
+
try {
|
|
20670
|
+
root = findProjectRoot();
|
|
20671
|
+
} catch (error) {
|
|
20672
|
+
fail(error.message);
|
|
20673
|
+
}
|
|
20476
20674
|
const impeccable = findInstalledPlugin(IMPECCABLE_PLUGIN);
|
|
20477
20675
|
const reasons = offReasons(root, impeccable);
|
|
20478
20676
|
const on = reasons.length === 0;
|
|
@@ -20545,7 +20743,7 @@ var init_feedback = __esm({
|
|
|
20545
20743
|
});
|
|
20546
20744
|
|
|
20547
20745
|
// src/lib/ackfile.ts
|
|
20548
|
-
import { existsSync as
|
|
20746
|
+
import { existsSync as existsSync15, readFileSync as readFileSync15 } from "node:fs";
|
|
20549
20747
|
function isAckDecision(value) {
|
|
20550
20748
|
return typeof value === "string" && ACK_DECISIONS.includes(value);
|
|
20551
20749
|
}
|
|
@@ -20597,7 +20795,7 @@ function writeAck(args) {
|
|
|
20597
20795
|
`That is a sidecar path, not a handoff file. Pass ${file.slice(0, -ACK_SUFFIX.length)} instead.`
|
|
20598
20796
|
);
|
|
20599
20797
|
}
|
|
20600
|
-
if (!
|
|
20798
|
+
if (!existsSync15(file)) {
|
|
20601
20799
|
throw new Error(`No handoff file at ${file}. Check the path the handoff gave you.`);
|
|
20602
20800
|
}
|
|
20603
20801
|
const record2 = {
|
|
@@ -20613,7 +20811,7 @@ function writeAck(args) {
|
|
|
20613
20811
|
function readAck(file) {
|
|
20614
20812
|
let raw;
|
|
20615
20813
|
try {
|
|
20616
|
-
raw =
|
|
20814
|
+
raw = readFileSync15(ackPath(file), "utf-8");
|
|
20617
20815
|
} catch (err) {
|
|
20618
20816
|
if (err.code === "ENOENT") return null;
|
|
20619
20817
|
throw err;
|
|
@@ -20645,11 +20843,11 @@ var init_ackfile = __esm({
|
|
|
20645
20843
|
});
|
|
20646
20844
|
|
|
20647
20845
|
// src/lib/ackwatch.ts
|
|
20648
|
-
import { readFileSync as
|
|
20649
|
-
import { homedir as
|
|
20650
|
-
import { basename as
|
|
20846
|
+
import { readFileSync as readFileSync16, statSync as statSync6 } from "node:fs";
|
|
20847
|
+
import { homedir as homedir4 } from "node:os";
|
|
20848
|
+
import { basename as basename6, join as join18 } from "node:path";
|
|
20651
20849
|
function transcriptPath(cwd, sessionId, projectsDir) {
|
|
20652
|
-
const base = projectsDir ?? join18(
|
|
20850
|
+
const base = projectsDir ?? join18(homedir4(), ".claude", "projects");
|
|
20653
20851
|
return join18(base, cwd.replace(/[^a-zA-Z0-9]/g, "-"), `${sessionId}.jsonl`);
|
|
20654
20852
|
}
|
|
20655
20853
|
function parseAck(text2) {
|
|
@@ -20758,7 +20956,7 @@ function mentionsFile(input, file) {
|
|
|
20758
20956
|
} catch {
|
|
20759
20957
|
return false;
|
|
20760
20958
|
}
|
|
20761
|
-
const name =
|
|
20959
|
+
const name = basename6(file);
|
|
20762
20960
|
return serialized.includes(file) || name.length > 0 && serialized.includes(name);
|
|
20763
20961
|
}
|
|
20764
20962
|
function isEngagement(record2, file) {
|
|
@@ -20831,7 +21029,7 @@ function analyze(lines, options) {
|
|
|
20831
21029
|
}
|
|
20832
21030
|
function readLines(path) {
|
|
20833
21031
|
try {
|
|
20834
|
-
return
|
|
21032
|
+
return readFileSync16(path, "utf-8").split("\n");
|
|
20835
21033
|
} catch (err) {
|
|
20836
21034
|
if (err.code === "ENOENT") return [];
|
|
20837
21035
|
throw err;
|
|
@@ -24160,8 +24358,8 @@ var require_pattern = __commonJS({
|
|
|
24160
24358
|
}
|
|
24161
24359
|
exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
|
|
24162
24360
|
function isAffectDepthOfReadingPattern(pattern) {
|
|
24163
|
-
const
|
|
24164
|
-
return endsWithSlashGlobStar(pattern) || isStaticPattern(
|
|
24361
|
+
const basename7 = path.basename(pattern);
|
|
24362
|
+
return endsWithSlashGlobStar(pattern) || isStaticPattern(basename7);
|
|
24165
24363
|
}
|
|
24166
24364
|
exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
|
|
24167
24365
|
function expandPatternsWithBraceExpansion(patterns) {
|
|
@@ -27182,7 +27380,7 @@ __export(lint_prose_exports, {
|
|
|
27182
27380
|
reportFile: () => reportFile,
|
|
27183
27381
|
summarize: () => summarize
|
|
27184
27382
|
});
|
|
27185
|
-
import { existsSync as
|
|
27383
|
+
import { existsSync as existsSync16, readFileSync as readFileSync17, statSync as statSync7 } from "node:fs";
|
|
27186
27384
|
import { join as join19, relative as relative6, resolve as resolve11 } from "node:path";
|
|
27187
27385
|
function loadVocabulary() {
|
|
27188
27386
|
const pluginRoot = findPluginRoot();
|
|
@@ -27220,7 +27418,7 @@ function collectFiles(paths, opts) {
|
|
|
27220
27418
|
if (opts.all) files.push(...markdownIn(opts.root));
|
|
27221
27419
|
for (const path of paths) {
|
|
27222
27420
|
const full = resolve11(path);
|
|
27223
|
-
if (!
|
|
27421
|
+
if (!existsSync16(full)) {
|
|
27224
27422
|
missing.push(path);
|
|
27225
27423
|
continue;
|
|
27226
27424
|
}
|
|
@@ -27262,7 +27460,7 @@ function displayPath(root, file) {
|
|
|
27262
27460
|
function reportFile(file, display, vocabulary) {
|
|
27263
27461
|
let text2;
|
|
27264
27462
|
try {
|
|
27265
|
-
text2 =
|
|
27463
|
+
text2 = readFileSync17(file, "utf-8");
|
|
27266
27464
|
} catch {
|
|
27267
27465
|
console.error(source_default.yellow(`Cannot read, skipping: ${display}`));
|
|
27268
27466
|
return { path: display, findings: [], unreadable: true };
|
|
@@ -27304,7 +27502,7 @@ function printSummary(summary, missing) {
|
|
|
27304
27502
|
}
|
|
27305
27503
|
function register11(program3) {
|
|
27306
27504
|
program3.command("lint-prose").description("Warn about prose that drifts from the writing standard").argument("[paths...]", "files or directories to lint").option("--all", "lint every .md file in the repository").action((paths, opts) => {
|
|
27307
|
-
const root =
|
|
27505
|
+
const root = locateProject()?.main ?? process.cwd();
|
|
27308
27506
|
if (paths.length === 0 && !opts.all) {
|
|
27309
27507
|
fail("Give one or more paths, or --all to lint the repository.");
|
|
27310
27508
|
}
|
|
@@ -27352,7 +27550,7 @@ var init_lint_prose = __esm({
|
|
|
27352
27550
|
|
|
27353
27551
|
// src/index.ts
|
|
27354
27552
|
init_esm();
|
|
27355
|
-
import { spawnSync as
|
|
27553
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
27356
27554
|
|
|
27357
27555
|
// src/lib/version.ts
|
|
27358
27556
|
import { readFileSync } from "node:fs";
|
|
@@ -27381,15 +27579,7 @@ function resolveVersion(startDir) {
|
|
|
27381
27579
|
}
|
|
27382
27580
|
|
|
27383
27581
|
// src/index.ts
|
|
27384
|
-
init_project_data();
|
|
27385
|
-
init_project();
|
|
27386
27582
|
init_utils();
|
|
27387
|
-
try {
|
|
27388
|
-
const root = findProjectRoot();
|
|
27389
|
-
if (root && !(["project-data", "design-review"].includes(process.argv[2] ?? "") && process.argv[3] === "migrate")) ensureProjectGitignore(root);
|
|
27390
|
-
} catch (error) {
|
|
27391
|
-
fail(error.message);
|
|
27392
|
-
}
|
|
27393
27583
|
var program2 = new Command().name("acstack").description("CLI for the ACStack agentic coding stack").version(resolveVersion(import.meta.dirname));
|
|
27394
27584
|
var commands = await Promise.all([
|
|
27395
27585
|
Promise.resolve().then(() => (init_project_data2(), project_data_exports)),
|
|
@@ -27417,7 +27607,7 @@ var known = new Set(
|
|
|
27417
27607
|
program2.commands.flatMap((c) => [c.name(), ...c.aliases()])
|
|
27418
27608
|
);
|
|
27419
27609
|
if (first && !first.startsWith("-") && !known.has(first)) {
|
|
27420
|
-
const result =
|
|
27610
|
+
const result = spawnSync4(`acstack-${first}`, process.argv.slice(3), {
|
|
27421
27611
|
stdio: "inherit"
|
|
27422
27612
|
});
|
|
27423
27613
|
const failedToSpawn = result.error !== void 0 && "code" in result.error;
|