harnessed 4.5.1 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -3
- package/bin/harnessed-inject-state.mjs +11 -0
- package/dist/cli.mjs +629 -298
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/manifests/optional/codegraph.yaml +6 -4
- package/manifests/optional/ecc.ee5-answers.json +13 -0
- package/manifests/optional/ecc.yaml +64 -0
- package/messages/en.json +4 -1
- package/messages/zh-Hans.json +4 -1
- package/package.json +1 -1
- package/workflows/capabilities.yaml +120 -0
package/dist/cli.mjs
CHANGED
|
@@ -38,7 +38,7 @@ var init_package = __esm({
|
|
|
38
38
|
"package.json"() {
|
|
39
39
|
package_default = {
|
|
40
40
|
name: "harnessed",
|
|
41
|
-
version: "4.
|
|
41
|
+
version: "4.7.0",
|
|
42
42
|
description: "AI coding harness package manager + composition orchestrator",
|
|
43
43
|
type: "module",
|
|
44
44
|
license: "Apache-2.0",
|
|
@@ -852,10 +852,92 @@ var init_origin_check = __esm({
|
|
|
852
852
|
"src/cli/lib/origin-check.ts"() {
|
|
853
853
|
}
|
|
854
854
|
});
|
|
855
|
-
function
|
|
855
|
+
function claudeDescriptor(home = homedir()) {
|
|
856
|
+
const claudeHome = join(home, ".claude");
|
|
857
|
+
return {
|
|
858
|
+
id: "claude",
|
|
859
|
+
homeDir: claudeHome,
|
|
860
|
+
stateRoot: join(claudeHome, "harnessed"),
|
|
861
|
+
settingsPath: join(claudeHome, "settings.json"),
|
|
862
|
+
skillsDir: join(claudeHome, "skills"),
|
|
863
|
+
commandsDir: join(claudeHome, "commands"),
|
|
864
|
+
pluginsRegistry: join(claudeHome, "plugins", "installed_plugins.json"),
|
|
865
|
+
// mcpConfigPath is a SIBLING of `.claude` (`<home>/.claude.json`), based on
|
|
866
|
+
// the same home base — not a child of homeDir.
|
|
867
|
+
mcpConfigPath: join(home, ".claude.json"),
|
|
868
|
+
// claude writes its env keys into JSON settings.json (capability present).
|
|
869
|
+
supportsEnvKeyWrite: true
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function codexDescriptor(home = homedir()) {
|
|
873
|
+
const codexHome = join(home, ".codex");
|
|
874
|
+
const configToml = join(codexHome, "config.toml");
|
|
875
|
+
return {
|
|
876
|
+
id: "codex",
|
|
877
|
+
homeDir: codexHome,
|
|
878
|
+
stateRoot: join(codexHome, "harnessed"),
|
|
879
|
+
settingsPath: configToml,
|
|
880
|
+
// SHARED convention dir, NOT <homeDir>/skills.
|
|
881
|
+
skillsDir: join(home, ".agents", "skills"),
|
|
882
|
+
commandsDir: join(codexHome, "prompts"),
|
|
883
|
+
pluginsRegistry: null,
|
|
884
|
+
mcpConfigPath: configToml,
|
|
885
|
+
supportsEnvKeyWrite: false
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function descriptorById(id, home) {
|
|
889
|
+
if (id === "claude") return claudeDescriptor(home);
|
|
890
|
+
if (id === "codex") return codexDescriptor(home);
|
|
891
|
+
return void 0;
|
|
892
|
+
}
|
|
893
|
+
function detectPlatform(home = homedir()) {
|
|
894
|
+
const base = claudeDescriptor(home);
|
|
856
895
|
const override = process.env.HARNESSED_ROOT_OVERRIDE;
|
|
857
|
-
if (override !== void 0 && override !== "") return override;
|
|
858
|
-
|
|
896
|
+
if (override !== void 0 && override !== "") return { ...base, stateRoot: override };
|
|
897
|
+
const envPlatform = process.env.HARNESSED_PLATFORM;
|
|
898
|
+
if (envPlatform !== void 0 && envPlatform !== "") {
|
|
899
|
+
const d = descriptorById(envPlatform, home);
|
|
900
|
+
if (d) return d;
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
const pin = readFileSync(join(base.stateRoot, ".platform"), "utf8").trim();
|
|
904
|
+
const d = descriptorById(pin, home);
|
|
905
|
+
if (d) return d;
|
|
906
|
+
} catch {
|
|
907
|
+
}
|
|
908
|
+
try {
|
|
909
|
+
if (existsSync(base.homeDir)) return base;
|
|
910
|
+
const codex = codexDescriptor(home);
|
|
911
|
+
if (existsSync(codex.homeDir)) return codex;
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
return base;
|
|
915
|
+
}
|
|
916
|
+
function getSettingsPath(home) {
|
|
917
|
+
return detectPlatform(home).settingsPath;
|
|
918
|
+
}
|
|
919
|
+
function getSkillsDir(home) {
|
|
920
|
+
return detectPlatform(home).skillsDir;
|
|
921
|
+
}
|
|
922
|
+
function getCommandsDir(home) {
|
|
923
|
+
return detectPlatform(home).commandsDir;
|
|
924
|
+
}
|
|
925
|
+
function getPluginsRegistry(home) {
|
|
926
|
+
return detectPlatform(home).pluginsRegistry;
|
|
927
|
+
}
|
|
928
|
+
function getMcpConfigPath(home) {
|
|
929
|
+
return detectPlatform(home).mcpConfigPath;
|
|
930
|
+
}
|
|
931
|
+
function harnessSkillsDirs(home) {
|
|
932
|
+
const dirs = [claudeDescriptor(home).skillsDir, codexDescriptor(home).skillsDir];
|
|
933
|
+
return [...new Set(dirs)];
|
|
934
|
+
}
|
|
935
|
+
var init_platform = __esm({
|
|
936
|
+
"src/installers/lib/platform.ts"() {
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
function getHarnessedRoot() {
|
|
940
|
+
return detectPlatform().stateRoot;
|
|
859
941
|
}
|
|
860
942
|
function harnessedSubdir(name) {
|
|
861
943
|
return join(getHarnessedRoot(), name);
|
|
@@ -890,6 +972,7 @@ function migrateLegacyHarnessedRoot() {
|
|
|
890
972
|
}
|
|
891
973
|
var init_harnessedRoot = __esm({
|
|
892
974
|
"src/installers/lib/harnessedRoot.ts"() {
|
|
975
|
+
init_platform();
|
|
893
976
|
}
|
|
894
977
|
});
|
|
895
978
|
function getBackupRoot() {
|
|
@@ -1291,7 +1374,19 @@ var init_currentWorkflow_v1 = __esm({
|
|
|
1291
1374
|
},
|
|
1292
1375
|
{ additionalProperties: false }
|
|
1293
1376
|
)
|
|
1294
|
-
)
|
|
1377
|
+
),
|
|
1378
|
+
// Phase 22 — smart reminders. Additive-optional flags set by `checkpoint
|
|
1379
|
+
// complete` (allResolved) and read by the G4 inject twins (booleans only; the
|
|
1380
|
+
// bin computes neither git nor thresholds). Old files without them Value.Check-pass
|
|
1381
|
+
// → NO schemaVersion bump.
|
|
1382
|
+
// ship_ready — there are unshipped commits since the last vX.Y.Z tag (git-derived,
|
|
1383
|
+
// recomputed each completion; self-heals to false after a release).
|
|
1384
|
+
// ship_commits — commit count since that tag (feeds the SHIP-READY hint text).
|
|
1385
|
+
// retro_due — phases_since_retro (store sidecar) has crossed the threshold;
|
|
1386
|
+
// cleared by `harnessed retro --done`.
|
|
1387
|
+
ship_ready: Type.Optional(Type.Boolean()),
|
|
1388
|
+
ship_commits: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
1389
|
+
retro_due: Type.Optional(Type.Boolean())
|
|
1295
1390
|
},
|
|
1296
1391
|
{ additionalProperties: false }
|
|
1297
1392
|
);
|
|
@@ -1309,6 +1404,7 @@ var init_schema2 = __esm({
|
|
|
1309
1404
|
// src/checkpoint/workflowStore.ts
|
|
1310
1405
|
var workflowStore_exports = {};
|
|
1311
1406
|
__export(workflowStore_exports, {
|
|
1407
|
+
RetroMetaEntry: () => RetroMetaEntry,
|
|
1312
1408
|
WorkflowStoreV1: () => WorkflowStoreV1,
|
|
1313
1409
|
listWorkflows: () => listWorkflows,
|
|
1314
1410
|
readStoreRaw: () => readStoreRaw,
|
|
@@ -1369,17 +1465,26 @@ async function listWorkflows() {
|
|
|
1369
1465
|
status: wf.status
|
|
1370
1466
|
}));
|
|
1371
1467
|
}
|
|
1372
|
-
var WorkflowStoreV1;
|
|
1468
|
+
var RetroMetaEntry, WorkflowStoreV1;
|
|
1373
1469
|
var init_workflowStore = __esm({
|
|
1374
1470
|
"src/checkpoint/workflowStore.ts"() {
|
|
1375
1471
|
init_harnessedRoot();
|
|
1376
1472
|
init_schemaVersion();
|
|
1377
1473
|
init_atomicWrite();
|
|
1378
1474
|
init_currentWorkflow_v1();
|
|
1475
|
+
RetroMetaEntry = Type.Object(
|
|
1476
|
+
{
|
|
1477
|
+
phases_since_retro: Type.Integer({ minimum: 0 }),
|
|
1478
|
+
last_retro_at: Type.Optional(Type.String({ minLength: 1 }))
|
|
1479
|
+
},
|
|
1480
|
+
{ additionalProperties: false }
|
|
1481
|
+
);
|
|
1379
1482
|
WorkflowStoreV1 = Type.Object(
|
|
1380
1483
|
{
|
|
1381
1484
|
schemaVersion: Type.Literal(SCHEMA_VERSIONS.workflowStore),
|
|
1382
|
-
workflows: Type.Record(Type.String(), CurrentWorkflowV1)
|
|
1485
|
+
workflows: Type.Record(Type.String(), CurrentWorkflowV1),
|
|
1486
|
+
// Additive-optional (no schemaVersion bump — old stores Value.Check-pass).
|
|
1487
|
+
retro_meta: Type.Optional(Type.Record(Type.String(), RetroMetaEntry))
|
|
1383
1488
|
},
|
|
1384
1489
|
{ additionalProperties: false }
|
|
1385
1490
|
);
|
|
@@ -1393,6 +1498,7 @@ __export(state_exports, {
|
|
|
1393
1498
|
WorkflowStateError: () => WorkflowStateError,
|
|
1394
1499
|
activate: () => activate,
|
|
1395
1500
|
complete: () => complete,
|
|
1501
|
+
mutateStore: () => mutateStore,
|
|
1396
1502
|
mutateSubProgress: () => mutateSubProgress,
|
|
1397
1503
|
pause: () => pause,
|
|
1398
1504
|
readCurrentWorkflow: () => readCurrentWorkflow,
|
|
@@ -1452,6 +1558,12 @@ async function mutateSubProgress(fn) {
|
|
|
1452
1558
|
await writeCurrentWorkflowUnlocked({ ...s, sub_progress: next });
|
|
1453
1559
|
});
|
|
1454
1560
|
}
|
|
1561
|
+
async function mutateStore(fn) {
|
|
1562
|
+
await withLock(async () => {
|
|
1563
|
+
const store = await readStoreRaw();
|
|
1564
|
+
await writeStoreRaw(fn(store));
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1455
1567
|
async function activate(phase, checkpointPath = null) {
|
|
1456
1568
|
await writeCurrentWorkflow({
|
|
1457
1569
|
schemaVersion: SCHEMA_VERSIONS.currentWorkflow,
|
|
@@ -2027,7 +2139,7 @@ function nameToYamlHintPath(name) {
|
|
|
2027
2139
|
function shouldOverwriteFile(content) {
|
|
2028
2140
|
return HARNESSED_MARKER_RX.test(content) || V3_4_3_SIGNATURE_SUB_RX.test(content) || V3_4_3_SIGNATURE_MASTER_RX.test(content);
|
|
2029
2141
|
}
|
|
2030
|
-
async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync,
|
|
2142
|
+
async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabilities, installedPlugins, installedUserSkills, writer, fileExists2 = existsSync, readFileSync13 = (p5) => readFileSync(p5, "utf8")) {
|
|
2031
2143
|
const results = [];
|
|
2032
2144
|
const aggregatedWarnings = /* @__PURE__ */ new Set();
|
|
2033
2145
|
for (const name of slashNames) {
|
|
@@ -2046,7 +2158,7 @@ async function writeAllCommands(slashNames, commandsDir, rolePrompts, capabiliti
|
|
|
2046
2158
|
if (fileExists2(path)) {
|
|
2047
2159
|
let existing = "";
|
|
2048
2160
|
try {
|
|
2049
|
-
existing =
|
|
2161
|
+
existing = readFileSync13(path);
|
|
2050
2162
|
} catch {
|
|
2051
2163
|
existing = "";
|
|
2052
2164
|
}
|
|
@@ -3801,6 +3913,91 @@ var init_compact = __esm({
|
|
|
3801
3913
|
}
|
|
3802
3914
|
});
|
|
3803
3915
|
|
|
3916
|
+
// src/checkpoint/shipReady.ts
|
|
3917
|
+
var shipReady_exports = {};
|
|
3918
|
+
__export(shipReady_exports, {
|
|
3919
|
+
collectShipReady: () => collectShipReady,
|
|
3920
|
+
defaultShipReady: () => defaultShipReady,
|
|
3921
|
+
pickLatestReleaseTag: () => pickLatestReleaseTag
|
|
3922
|
+
});
|
|
3923
|
+
function pickLatestReleaseTag(tags) {
|
|
3924
|
+
let best = null;
|
|
3925
|
+
for (const tag of tags) {
|
|
3926
|
+
const m = RELEASE_TAG.exec(tag.trim());
|
|
3927
|
+
if (!m) continue;
|
|
3928
|
+
const n = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
3929
|
+
if (!best || n[0] > best.n[0] || n[0] === best.n[0] && n[1] > best.n[1] || n[0] === best.n[0] && n[1] === best.n[1] && n[2] > best.n[2]) {
|
|
3930
|
+
best = { tag: tag.trim(), n };
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
return best?.tag ?? null;
|
|
3934
|
+
}
|
|
3935
|
+
function collectShipReady(tags, countSince) {
|
|
3936
|
+
const baseTag = pickLatestReleaseTag(tags);
|
|
3937
|
+
const commits = countSince(baseTag);
|
|
3938
|
+
return { ready: commits > 0, commits, baseTag };
|
|
3939
|
+
}
|
|
3940
|
+
function defaultShipReady(cwd) {
|
|
3941
|
+
let tags = [];
|
|
3942
|
+
try {
|
|
3943
|
+
tags = execFileSync("git", ["tag", "-l"], { cwd, encoding: "utf8" }).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
3944
|
+
} catch {
|
|
3945
|
+
return { ready: false, commits: 0, baseTag: null };
|
|
3946
|
+
}
|
|
3947
|
+
const countSince = (tag) => {
|
|
3948
|
+
try {
|
|
3949
|
+
const range = tag ? `${tag}..HEAD` : "HEAD";
|
|
3950
|
+
const out = execFileSync("git", ["rev-list", range, "--count"], {
|
|
3951
|
+
cwd,
|
|
3952
|
+
encoding: "utf8"
|
|
3953
|
+
}).trim();
|
|
3954
|
+
const n = Number(out);
|
|
3955
|
+
return Number.isFinite(n) ? n : 0;
|
|
3956
|
+
} catch {
|
|
3957
|
+
return 0;
|
|
3958
|
+
}
|
|
3959
|
+
};
|
|
3960
|
+
return collectShipReady(tags, countSince);
|
|
3961
|
+
}
|
|
3962
|
+
var RELEASE_TAG;
|
|
3963
|
+
var init_shipReady = __esm({
|
|
3964
|
+
"src/checkpoint/shipReady.ts"() {
|
|
3965
|
+
RELEASE_TAG = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
3966
|
+
}
|
|
3967
|
+
});
|
|
3968
|
+
|
|
3969
|
+
// src/checkpoint/retroMeta.ts
|
|
3970
|
+
var retroMeta_exports = {};
|
|
3971
|
+
__export(retroMeta_exports, {
|
|
3972
|
+
DEFAULT_RETRO_THRESHOLD: () => DEFAULT_RETRO_THRESHOLD,
|
|
3973
|
+
incrementPhases: () => incrementPhases,
|
|
3974
|
+
isRetroDue: () => isRetroDue,
|
|
3975
|
+
resetRetro: () => resetRetro,
|
|
3976
|
+
retroThreshold: () => retroThreshold
|
|
3977
|
+
});
|
|
3978
|
+
function retroThreshold(env) {
|
|
3979
|
+
const raw = env.HARNESSED_RETRO_PHASE_THRESHOLD;
|
|
3980
|
+
if (raw === void 0) return DEFAULT_RETRO_THRESHOLD;
|
|
3981
|
+
const n = Number(raw);
|
|
3982
|
+
return Number.isInteger(n) && n > 0 ? n : DEFAULT_RETRO_THRESHOLD;
|
|
3983
|
+
}
|
|
3984
|
+
function incrementPhases(entry) {
|
|
3985
|
+
if (!entry) return { phases_since_retro: 1 };
|
|
3986
|
+
return entry.last_retro_at ? { phases_since_retro: entry.phases_since_retro + 1, last_retro_at: entry.last_retro_at } : { phases_since_retro: entry.phases_since_retro + 1 };
|
|
3987
|
+
}
|
|
3988
|
+
function isRetroDue(count, threshold) {
|
|
3989
|
+
return count >= threshold;
|
|
3990
|
+
}
|
|
3991
|
+
function resetRetro(now) {
|
|
3992
|
+
return { phases_since_retro: 0, last_retro_at: now };
|
|
3993
|
+
}
|
|
3994
|
+
var DEFAULT_RETRO_THRESHOLD;
|
|
3995
|
+
var init_retroMeta = __esm({
|
|
3996
|
+
"src/checkpoint/retroMeta.ts"() {
|
|
3997
|
+
DEFAULT_RETRO_THRESHOLD = 5;
|
|
3998
|
+
}
|
|
3999
|
+
});
|
|
4000
|
+
|
|
3804
4001
|
// src/checkpoint/breakLoop.ts
|
|
3805
4002
|
var breakLoop_exports = {};
|
|
3806
4003
|
__export(breakLoop_exports, {
|
|
@@ -4259,7 +4456,7 @@ var init_check_mattpocock_skills = __esm({
|
|
|
4259
4456
|
}
|
|
4260
4457
|
});
|
|
4261
4458
|
function getUserClaudeJsonPath() {
|
|
4262
|
-
return
|
|
4459
|
+
return getMcpConfigPath();
|
|
4263
4460
|
}
|
|
4264
4461
|
async function readUserClaudeJson() {
|
|
4265
4462
|
const path = getUserClaudeJsonPath();
|
|
@@ -4285,21 +4482,20 @@ async function isMcpServerRegistered(name) {
|
|
|
4285
4482
|
return Object.hasOwn(servers, name);
|
|
4286
4483
|
}
|
|
4287
4484
|
async function isPluginRegistered(pluginName) {
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
if (
|
|
4295
|
-
|
|
4485
|
+
const registryPath = getPluginsRegistry();
|
|
4486
|
+
if (registryPath !== null) {
|
|
4487
|
+
try {
|
|
4488
|
+
const raw = await readFile(registryPath, "utf8");
|
|
4489
|
+
const parsed = JSON.parse(raw);
|
|
4490
|
+
const plugins = parsed.plugins;
|
|
4491
|
+
if (plugins && typeof plugins === "object") {
|
|
4492
|
+
if (Object.hasOwn(plugins, pluginName)) return true;
|
|
4493
|
+
if (Object.keys(plugins).some((k) => k.split("@")[0] === pluginName)) return true;
|
|
4494
|
+
}
|
|
4495
|
+
} catch {
|
|
4296
4496
|
}
|
|
4297
|
-
} catch {
|
|
4298
4497
|
}
|
|
4299
|
-
for (const path of [
|
|
4300
|
-
join(homedir(), ".claude", "settings.json"),
|
|
4301
|
-
join(homedir(), ".claude.json")
|
|
4302
|
-
]) {
|
|
4498
|
+
for (const path of [getSettingsPath(), getMcpConfigPath()]) {
|
|
4303
4499
|
try {
|
|
4304
4500
|
const raw = await readFile(path, "utf8");
|
|
4305
4501
|
const parsed = JSON.parse(raw);
|
|
@@ -4315,6 +4511,7 @@ async function isPluginRegistered(pluginName) {
|
|
|
4315
4511
|
}
|
|
4316
4512
|
var init_readClaudeConfig = __esm({
|
|
4317
4513
|
"src/installers/lib/readClaudeConfig.ts"() {
|
|
4514
|
+
init_platform();
|
|
4318
4515
|
}
|
|
4319
4516
|
});
|
|
4320
4517
|
|
|
@@ -4713,6 +4910,7 @@ var init_ccHookAdd2 = __esm({
|
|
|
4713
4910
|
init_confirm();
|
|
4714
4911
|
init_diff();
|
|
4715
4912
|
init_err();
|
|
4913
|
+
init_platform();
|
|
4716
4914
|
init_preflight();
|
|
4717
4915
|
init_state2();
|
|
4718
4916
|
installCcHookAdd = async (ctx) => {
|
|
@@ -4736,10 +4934,10 @@ var init_ccHookAdd2 = __esm({
|
|
|
4736
4934
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed", "preflight");
|
|
4737
4935
|
return { ok: false, phase: "preflight", error: e };
|
|
4738
4936
|
}
|
|
4739
|
-
const
|
|
4937
|
+
const settingsPath = getSettingsPath();
|
|
4740
4938
|
let existing;
|
|
4741
4939
|
try {
|
|
4742
|
-
existing = await readFile(
|
|
4940
|
+
existing = await readFile(settingsPath, "utf8");
|
|
4743
4941
|
} catch {
|
|
4744
4942
|
existing = null;
|
|
4745
4943
|
}
|
|
@@ -4770,7 +4968,7 @@ var init_ccHookAdd2 = __esm({
|
|
|
4770
4968
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
4771
4969
|
`;
|
|
4772
4970
|
const plan = {
|
|
4773
|
-
files: [{ target:
|
|
4971
|
+
files: [{ target: settingsPath, scope: "HOME", oldText: existing ?? "", newText }]
|
|
4774
4972
|
};
|
|
4775
4973
|
if (!ctx.opts.quiet) process.stdout.write(renderDiff(plan, ctx));
|
|
4776
4974
|
const conf = await confirmAt("L3", { ...ctx});
|
|
@@ -4781,10 +4979,10 @@ var init_ccHookAdd2 = __esm({
|
|
|
4781
4979
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
4782
4980
|
const bk = await backup(plan, ctx);
|
|
4783
4981
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
4784
|
-
await writeFile(
|
|
4982
|
+
await writeFile(settingsPath, newText);
|
|
4785
4983
|
let verify;
|
|
4786
4984
|
try {
|
|
4787
|
-
verify = JSON.parse(await readFile(
|
|
4985
|
+
verify = JSON.parse(await readFile(settingsPath, "utf8"));
|
|
4788
4986
|
} catch (e) {
|
|
4789
4987
|
return {
|
|
4790
4988
|
ok: false,
|
|
@@ -4812,7 +5010,7 @@ var init_ccHookAdd2 = __esm({
|
|
|
4812
5010
|
};
|
|
4813
5011
|
}
|
|
4814
5012
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
|
|
4815
|
-
return { ok: true, backupId: bk.backupId, appliedFiles: [
|
|
5013
|
+
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
|
|
4816
5014
|
};
|
|
4817
5015
|
}
|
|
4818
5016
|
});
|
|
@@ -4868,10 +5066,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
|
|
|
4868
5066
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
4869
5067
|
stderr += chunk;
|
|
4870
5068
|
});
|
|
4871
|
-
return await new Promise((
|
|
5069
|
+
return await new Promise((resolve18) => {
|
|
4872
5070
|
const timer = setTimeout(() => {
|
|
4873
5071
|
child.kill("SIGKILL");
|
|
4874
|
-
|
|
5072
|
+
resolve18({
|
|
4875
5073
|
ok: false,
|
|
4876
5074
|
phase: "spawn",
|
|
4877
5075
|
error: {
|
|
@@ -4887,7 +5085,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
|
|
|
4887
5085
|
child.on("error", (err2) => {
|
|
4888
5086
|
clearTimeout(timer);
|
|
4889
5087
|
const bashMissing = triedBash && err2.code === "ENOENT";
|
|
4890
|
-
|
|
5088
|
+
resolve18({
|
|
4891
5089
|
ok: false,
|
|
4892
5090
|
phase: "spawn",
|
|
4893
5091
|
error: {
|
|
@@ -4902,7 +5100,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs, opts) {
|
|
|
4902
5100
|
});
|
|
4903
5101
|
child.on("close", (code) => {
|
|
4904
5102
|
clearTimeout(timer);
|
|
4905
|
-
|
|
5103
|
+
resolve18({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
4906
5104
|
});
|
|
4907
5105
|
});
|
|
4908
5106
|
}
|
|
@@ -4942,7 +5140,7 @@ async function detectNative(ctx) {
|
|
|
4942
5140
|
const indicators = INSTALLED_INDICATORS[name];
|
|
4943
5141
|
if (indicators) {
|
|
4944
5142
|
for (const ind of indicators) {
|
|
4945
|
-
const dir = join(
|
|
5143
|
+
const dir = join(getSkillsDir(), ind);
|
|
4946
5144
|
try {
|
|
4947
5145
|
await access(dir);
|
|
4948
5146
|
return true;
|
|
@@ -4961,8 +5159,8 @@ async function detectNative(ctx) {
|
|
|
4961
5159
|
}
|
|
4962
5160
|
if (method === "npx-skill-installer") {
|
|
4963
5161
|
const skillName = extractSkillName(cmd, name);
|
|
4964
|
-
for (const
|
|
4965
|
-
const skillMd = join(
|
|
5162
|
+
for (const skillsDir of harnessSkillsDirs()) {
|
|
5163
|
+
const skillMd = join(skillsDir, skillName, "SKILL.md");
|
|
4966
5164
|
try {
|
|
4967
5165
|
await access(skillMd);
|
|
4968
5166
|
return true;
|
|
@@ -4981,7 +5179,7 @@ async function detectNative(ctx) {
|
|
|
4981
5179
|
}
|
|
4982
5180
|
}
|
|
4983
5181
|
if (method === "npm-cli") {
|
|
4984
|
-
const skillDir = join(
|
|
5182
|
+
const skillDir = join(getSkillsDir(), name);
|
|
4985
5183
|
try {
|
|
4986
5184
|
await access(skillDir);
|
|
4987
5185
|
return true;
|
|
@@ -5015,6 +5213,7 @@ async function isAlreadyInstalled(ctx, opts = {}) {
|
|
|
5015
5213
|
var IDEMPOTENT_CHECK_TIMEOUT_MS, INSTALLED_INDICATORS;
|
|
5016
5214
|
var init_idempotent = __esm({
|
|
5017
5215
|
"src/installers/lib/idempotent.ts"() {
|
|
5216
|
+
init_platform();
|
|
5018
5217
|
init_readClaudeConfig();
|
|
5019
5218
|
init_spawn();
|
|
5020
5219
|
IDEMPOTENT_CHECK_TIMEOUT_MS = 1e4;
|
|
@@ -5025,7 +5224,7 @@ var init_idempotent = __esm({
|
|
|
5025
5224
|
}
|
|
5026
5225
|
});
|
|
5027
5226
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
5028
|
-
return new Promise((
|
|
5227
|
+
return new Promise((resolve18) => {
|
|
5029
5228
|
const isWin = process.platform === "win32";
|
|
5030
5229
|
const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
|
|
5031
5230
|
let stderr = "";
|
|
@@ -5034,15 +5233,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
5034
5233
|
});
|
|
5035
5234
|
const timer = setTimeout(() => {
|
|
5036
5235
|
child.kill("SIGKILL");
|
|
5037
|
-
|
|
5236
|
+
resolve18({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
|
|
5038
5237
|
}, timeoutMs);
|
|
5039
5238
|
child.on("error", (e) => {
|
|
5040
5239
|
clearTimeout(timer);
|
|
5041
|
-
|
|
5240
|
+
resolve18({ exitCode: -1, stderr: `${stderr}${e.message}` });
|
|
5042
5241
|
});
|
|
5043
5242
|
child.on("close", (code) => {
|
|
5044
5243
|
clearTimeout(timer);
|
|
5045
|
-
|
|
5244
|
+
resolve18({ exitCode: code ?? -1, stderr });
|
|
5046
5245
|
});
|
|
5047
5246
|
});
|
|
5048
5247
|
}
|
|
@@ -5212,7 +5411,7 @@ ${newEntry}
|
|
|
5212
5411
|
}
|
|
5213
5412
|
});
|
|
5214
5413
|
function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
5215
|
-
return new Promise((
|
|
5414
|
+
return new Promise((resolve18) => {
|
|
5216
5415
|
const isWin = process.platform === "win32";
|
|
5217
5416
|
const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
|
|
5218
5417
|
let stdout2 = "";
|
|
@@ -5221,15 +5420,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
|
5221
5420
|
});
|
|
5222
5421
|
const timer = setTimeout(() => {
|
|
5223
5422
|
child.kill("SIGKILL");
|
|
5224
|
-
|
|
5423
|
+
resolve18({ sha: "", exit: -1 });
|
|
5225
5424
|
}, timeoutMs);
|
|
5226
5425
|
child.on("error", () => {
|
|
5227
5426
|
clearTimeout(timer);
|
|
5228
|
-
|
|
5427
|
+
resolve18({ sha: "", exit: -1 });
|
|
5229
5428
|
});
|
|
5230
5429
|
child.on("close", (code) => {
|
|
5231
5430
|
clearTimeout(timer);
|
|
5232
|
-
|
|
5431
|
+
resolve18({ sha: stdout2.trim(), exit: code ?? -1 });
|
|
5233
5432
|
});
|
|
5234
5433
|
});
|
|
5235
5434
|
}
|
|
@@ -6170,8 +6369,14 @@ var init_install_base = __esm({
|
|
|
6170
6369
|
// src/checkpoint/nextStep.ts
|
|
6171
6370
|
var nextStep_exports = {};
|
|
6172
6371
|
__export(nextStep_exports, {
|
|
6372
|
+
resolveAutoTransition: () => resolveAutoTransition,
|
|
6173
6373
|
resolveNext: () => resolveNext
|
|
6174
6374
|
});
|
|
6375
|
+
function resolveAutoTransition(envelopeValue, env = process.env.HARNESSED_AUTO_TRANSITION) {
|
|
6376
|
+
if (env === "true") return true;
|
|
6377
|
+
if (env === "false") return false;
|
|
6378
|
+
return envelopeValue ?? true;
|
|
6379
|
+
}
|
|
6175
6380
|
function resolveNext(ledger, autoTransition) {
|
|
6176
6381
|
const sub = nextPending(ledger);
|
|
6177
6382
|
if (sub === null) return { next: "done" };
|
|
@@ -6768,7 +6973,7 @@ function renderHumanTable(records) {
|
|
|
6768
6973
|
}
|
|
6769
6974
|
}
|
|
6770
6975
|
function pipeToJq(filterExpr, lines) {
|
|
6771
|
-
return new Promise((
|
|
6976
|
+
return new Promise((resolve18, reject) => {
|
|
6772
6977
|
const child = spawn("jq", [filterExpr], {
|
|
6773
6978
|
stdio: ["pipe", "inherit", "inherit"],
|
|
6774
6979
|
windowsHide: true
|
|
@@ -6777,12 +6982,12 @@ function pipeToJq(filterExpr, lines) {
|
|
|
6777
6982
|
const e = err2;
|
|
6778
6983
|
if (e.code === "ENOENT") {
|
|
6779
6984
|
console.error(t("audit_log.jq_missing"));
|
|
6780
|
-
|
|
6985
|
+
resolve18(1);
|
|
6781
6986
|
} else {
|
|
6782
6987
|
reject(err2);
|
|
6783
6988
|
}
|
|
6784
6989
|
});
|
|
6785
|
-
child.on("close", (code) =>
|
|
6990
|
+
child.on("close", (code) => resolve18(code ?? 0));
|
|
6786
6991
|
child.stdin.write(lines.join("\n"));
|
|
6787
6992
|
child.stdin.end();
|
|
6788
6993
|
});
|
|
@@ -6999,6 +7204,35 @@ function registerCheckpoint(program2) {
|
|
|
6999
7204
|
);
|
|
7000
7205
|
}
|
|
7001
7206
|
}
|
|
7207
|
+
if (allResolved) {
|
|
7208
|
+
try {
|
|
7209
|
+
const { defaultShipReady: defaultShipReady2 } = await Promise.resolve().then(() => (init_shipReady(), shipReady_exports));
|
|
7210
|
+
const { incrementPhases: incrementPhases2, isRetroDue: isRetroDue2, retroThreshold: retroThreshold2 } = await Promise.resolve().then(() => (init_retroMeta(), retroMeta_exports));
|
|
7211
|
+
const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow4, writeCurrentWorkflow: writeCurrentWorkflow3 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7212
|
+
const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
|
|
7213
|
+
const ship = defaultShipReady2(process.cwd());
|
|
7214
|
+
const key = repoKey2();
|
|
7215
|
+
const threshold = retroThreshold2(process.env);
|
|
7216
|
+
let retroDue = false;
|
|
7217
|
+
await mutateStore2((store) => {
|
|
7218
|
+
const meta = { ...store.retro_meta ?? {} };
|
|
7219
|
+
const next = incrementPhases2(meta[key]);
|
|
7220
|
+
meta[key] = next;
|
|
7221
|
+
retroDue = isRetroDue2(next.phases_since_retro, threshold);
|
|
7222
|
+
return { ...store, retro_meta: meta };
|
|
7223
|
+
});
|
|
7224
|
+
const latest2 = await readCurrentWorkflow4();
|
|
7225
|
+
if (latest2) {
|
|
7226
|
+
await writeCurrentWorkflow3({
|
|
7227
|
+
...latest2,
|
|
7228
|
+
ship_ready: ship.ready,
|
|
7229
|
+
ship_commits: ship.commits,
|
|
7230
|
+
retro_due: retroDue
|
|
7231
|
+
});
|
|
7232
|
+
}
|
|
7233
|
+
} catch {
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7002
7236
|
console.log(`[harnessed] checkpoint complete: ${sub} (evidence: ${evidenceStatus})`);
|
|
7003
7237
|
process.exit(0);
|
|
7004
7238
|
return;
|
|
@@ -7315,24 +7549,26 @@ function registerInstall(program2) {
|
|
|
7315
7549
|
const { resolveAlias: resolveAlias2 } = await Promise.resolve().then(() => (init_aliases(), aliases_exports));
|
|
7316
7550
|
const resolvedName = resolveAlias2(name) ?? name;
|
|
7317
7551
|
checkPathSafe(resolvedName);
|
|
7318
|
-
const
|
|
7319
|
-
|
|
7552
|
+
const candidatePaths = ["tools", "skill-packs", "optional"].map(
|
|
7553
|
+
(dir) => resolve(getPackageRoot(), `manifests/${dir}/${resolvedName}.yaml`)
|
|
7554
|
+
);
|
|
7320
7555
|
let yamlSrc;
|
|
7321
|
-
let chosenPath
|
|
7322
|
-
|
|
7323
|
-
yamlSrc = await readFile(manifestPath, "utf8");
|
|
7324
|
-
} catch {
|
|
7556
|
+
let chosenPath;
|
|
7557
|
+
for (const p5 of candidatePaths) {
|
|
7325
7558
|
try {
|
|
7326
|
-
yamlSrc = await readFile(
|
|
7327
|
-
chosenPath =
|
|
7559
|
+
yamlSrc = await readFile(p5, "utf8");
|
|
7560
|
+
chosenPath = p5;
|
|
7561
|
+
break;
|
|
7328
7562
|
} catch {
|
|
7329
|
-
console.error(
|
|
7330
|
-
`${t("install.manifest_not_found", { name: resolvedName })}
|
|
7331
|
-
${t("install.manifest_not_found.fix", { name: resolvedName })}`
|
|
7332
|
-
);
|
|
7333
|
-
process.exit(1);
|
|
7334
7563
|
}
|
|
7335
7564
|
}
|
|
7565
|
+
if (yamlSrc === void 0 || chosenPath === void 0) {
|
|
7566
|
+
console.error(
|
|
7567
|
+
`${t("install.manifest_not_found", { name: resolvedName })}
|
|
7568
|
+
${t("install.manifest_not_found.fix", { name: resolvedName })}`
|
|
7569
|
+
);
|
|
7570
|
+
process.exit(1);
|
|
7571
|
+
}
|
|
7336
7572
|
const v = validateManifestFile(yamlSrc, chosenPath);
|
|
7337
7573
|
if (!v.ok) {
|
|
7338
7574
|
for (const e of v.errors) console.error(`error: ${e.message} at ${e.path}`);
|
|
@@ -7392,6 +7628,181 @@ function registerLearn(program2) {
|
|
|
7392
7628
|
process.exit(0);
|
|
7393
7629
|
});
|
|
7394
7630
|
}
|
|
7631
|
+
|
|
7632
|
+
// src/cli/lib/here.ts
|
|
7633
|
+
init_nextStep();
|
|
7634
|
+
init_harnessedRoot();
|
|
7635
|
+
|
|
7636
|
+
// src/cli/status.ts
|
|
7637
|
+
init_evidence();
|
|
7638
|
+
init_ledger();
|
|
7639
|
+
init_state();
|
|
7640
|
+
init_harnessedRoot();
|
|
7641
|
+
init_state2();
|
|
7642
|
+
function statusMarker(status) {
|
|
7643
|
+
switch (status) {
|
|
7644
|
+
case "done":
|
|
7645
|
+
return "\u2705 done";
|
|
7646
|
+
case "pending":
|
|
7647
|
+
return "\u23F3 pending";
|
|
7648
|
+
case "failed":
|
|
7649
|
+
return "\u2717 failed";
|
|
7650
|
+
case "skipped":
|
|
7651
|
+
return "\u2B1C skipped";
|
|
7652
|
+
case "rejected":
|
|
7653
|
+
return "\u{1F6AB} rejected";
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
function evidenceLabel(entry) {
|
|
7657
|
+
switch (entry.evidence_status) {
|
|
7658
|
+
case "verified":
|
|
7659
|
+
return "evidence: verified";
|
|
7660
|
+
case "overridden":
|
|
7661
|
+
return "evidence: overridden (--force)";
|
|
7662
|
+
default:
|
|
7663
|
+
return "evidence: none_declared";
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
async function buildRecoverLines(workflow, ledger) {
|
|
7667
|
+
const lines = [];
|
|
7668
|
+
if (!workflow || ledger.length === 0) {
|
|
7669
|
+
lines.push("no ledger \u2014 run gates + start");
|
|
7670
|
+
return lines;
|
|
7671
|
+
}
|
|
7672
|
+
lines.push(`workflow: ${workflow.phase} (${workflow.status}) started ${workflow.started_at}`);
|
|
7673
|
+
const next = nextPending(ledger);
|
|
7674
|
+
for (const e of ledger) {
|
|
7675
|
+
const marker = statusMarker(e.status);
|
|
7676
|
+
let suffix = "";
|
|
7677
|
+
if (e.status === "skipped") {
|
|
7678
|
+
suffix = e.reason ? ` (skipped: ${e.reason})` : "";
|
|
7679
|
+
} else if (e.status === "done") {
|
|
7680
|
+
suffix = ` ${evidenceLabel(e)}`;
|
|
7681
|
+
}
|
|
7682
|
+
const arrow = e.sub === next ? " \u2190 next" : "";
|
|
7683
|
+
lines.push(` ${e.sub} ${marker}${suffix}${arrow}`);
|
|
7684
|
+
}
|
|
7685
|
+
if (next) {
|
|
7686
|
+
lines.push(`\u2192 next: harnessed prompt ${next}`);
|
|
7687
|
+
} else {
|
|
7688
|
+
lines.push("\u2192 all subs resolved (no pending work)");
|
|
7689
|
+
}
|
|
7690
|
+
for (const e of ledger) {
|
|
7691
|
+
if (!e.evidence || e.evidence.length === 0) continue;
|
|
7692
|
+
const drift = await detectDrift(e.evidence);
|
|
7693
|
+
for (const d of drift) {
|
|
7694
|
+
const now = d.now === "" ? "missing" : `${d.now.slice(0, 7)}\u2026`;
|
|
7695
|
+
lines.push(`\u26A0 drift: ${d.path} sha256 changed (was ${d.was.slice(0, 7)}\u2026, now ${now})`);
|
|
7696
|
+
}
|
|
7697
|
+
}
|
|
7698
|
+
return lines;
|
|
7699
|
+
}
|
|
7700
|
+
function registerStatus(program2) {
|
|
7701
|
+
program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").option("--recover", "structured recovery view of the active workflow ledger (v5.0 Spec 1 B)").action(async (opts) => {
|
|
7702
|
+
if (opts.recover) {
|
|
7703
|
+
const wf = await readCurrentWorkflow();
|
|
7704
|
+
const ledger = wf?.sub_progress ?? [];
|
|
7705
|
+
const lines = await buildRecoverLines(wf, ledger);
|
|
7706
|
+
for (const l of lines) console.log(l);
|
|
7707
|
+
return;
|
|
7708
|
+
}
|
|
7709
|
+
const state = await readState(process.cwd());
|
|
7710
|
+
const names = Object.keys(state.installed).sort();
|
|
7711
|
+
if (names.length === 0) {
|
|
7712
|
+
console.log(t("status.no_installs", { path: harnessedFile("state.json") }));
|
|
7713
|
+
} else {
|
|
7714
|
+
for (const n of names) {
|
|
7715
|
+
const e = state.installed[n];
|
|
7716
|
+
if (!e) continue;
|
|
7717
|
+
console.log(`${n} @ ${e.version} (installed ${e.installedAt})`);
|
|
7718
|
+
}
|
|
7719
|
+
console.log(t("status.summary_installs", { count: names.length }));
|
|
7720
|
+
}
|
|
7721
|
+
const lockPath = harnessedFile(".lock");
|
|
7722
|
+
try {
|
|
7723
|
+
const isLocked = await lockfile.check(getHarnessedRoot(), {
|
|
7724
|
+
lockfilePath: lockPath,
|
|
7725
|
+
stale: 1e4
|
|
7726
|
+
});
|
|
7727
|
+
if (isLocked) {
|
|
7728
|
+
const s = await stat(lockPath);
|
|
7729
|
+
const ageMs = Date.now() - s.mtime.getTime();
|
|
7730
|
+
const stale = ageMs > 1e4;
|
|
7731
|
+
console.log(
|
|
7732
|
+
t("status.lock_held", {
|
|
7733
|
+
since: s.mtime.toISOString(),
|
|
7734
|
+
staleSuffix: stale ? t("status.lock_held.stale_suffix") : ""
|
|
7735
|
+
})
|
|
7736
|
+
);
|
|
7737
|
+
console.log(t("status.lock_release_hint", { path: lockPath }));
|
|
7738
|
+
} else {
|
|
7739
|
+
console.log(t("status.lock_free"));
|
|
7740
|
+
}
|
|
7741
|
+
} catch {
|
|
7742
|
+
}
|
|
7743
|
+
});
|
|
7744
|
+
}
|
|
7745
|
+
|
|
7746
|
+
// src/cli/lib/here.ts
|
|
7747
|
+
function parseBareInvocation(argvRest) {
|
|
7748
|
+
const tokens = [];
|
|
7749
|
+
for (let i = 0; i < argvRest.length; i++) {
|
|
7750
|
+
const a = argvRest[i];
|
|
7751
|
+
if (a === "--lang") {
|
|
7752
|
+
i++;
|
|
7753
|
+
continue;
|
|
7754
|
+
}
|
|
7755
|
+
if (a?.startsWith("--lang=")) continue;
|
|
7756
|
+
if (a !== void 0) tokens.push(a);
|
|
7757
|
+
}
|
|
7758
|
+
if (tokens.length === 0) return { here: true, json: false };
|
|
7759
|
+
if (tokens.length === 1 && tokens[0] === "--json") return { here: true, json: true };
|
|
7760
|
+
return { here: false, json: false };
|
|
7761
|
+
}
|
|
7762
|
+
async function buildHereView(wf, ledger, auto) {
|
|
7763
|
+
if (!wf) {
|
|
7764
|
+
return {
|
|
7765
|
+
lines: [
|
|
7766
|
+
t("here.no_workflow", { path: harnessedFile("workflows.json") }),
|
|
7767
|
+
t("here.no_workflow.hint")
|
|
7768
|
+
],
|
|
7769
|
+
json: { active: false }
|
|
7770
|
+
};
|
|
7771
|
+
}
|
|
7772
|
+
const body = await buildRecoverLines(wf, ledger);
|
|
7773
|
+
const step = resolveNext(ledger, auto);
|
|
7774
|
+
const lines = [
|
|
7775
|
+
...body,
|
|
7776
|
+
`NEXT: ${step.next}`,
|
|
7777
|
+
...step.sub ? [`\u2192 run: harnessed prompt ${step.sub}`] : []
|
|
7778
|
+
];
|
|
7779
|
+
return {
|
|
7780
|
+
lines,
|
|
7781
|
+
json: {
|
|
7782
|
+
active: true,
|
|
7783
|
+
phase: wf.phase,
|
|
7784
|
+
status: wf.status,
|
|
7785
|
+
started_at: wf.started_at,
|
|
7786
|
+
next: step.next,
|
|
7787
|
+
sub: step.sub,
|
|
7788
|
+
hint: step.hint,
|
|
7789
|
+
sub_progress: ledger
|
|
7790
|
+
}
|
|
7791
|
+
};
|
|
7792
|
+
}
|
|
7793
|
+
async function runHere(opts) {
|
|
7794
|
+
const { readCurrentWorkflow: readCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7795
|
+
const wf = await readCurrentWorkflow2();
|
|
7796
|
+
const ledger = wf?.sub_progress ?? [];
|
|
7797
|
+
const auto = resolveAutoTransition(wf?.auto_transition);
|
|
7798
|
+
const view = await buildHereView(wf, ledger, auto);
|
|
7799
|
+
if (opts.json) {
|
|
7800
|
+
console.log(JSON.stringify(view.json, null, 2));
|
|
7801
|
+
} else {
|
|
7802
|
+
for (const l of view.lines) console.log(l);
|
|
7803
|
+
}
|
|
7804
|
+
process.exit(0);
|
|
7805
|
+
}
|
|
7395
7806
|
var QA = [
|
|
7396
7807
|
{ q: "\u2460 \u662F\u771F reusable surface \u8FD8\u662F\u4E34\u65F6 wrapper?", f: "q1_reusable_surface" },
|
|
7397
7808
|
{ q: "\u2461 \u4E0A\u6E38\u540D\u5B57 fit \u9879\u76EE shape \u5417? \u6709\u73B0\u6709\u547D\u540D\u51B2\u7A81\u5417?", f: "q2_name_fit" },
|
|
@@ -7445,21 +7856,15 @@ function registerManifestAdd(program2) {
|
|
|
7445
7856
|
}
|
|
7446
7857
|
|
|
7447
7858
|
// src/cli/next.ts
|
|
7448
|
-
function resolveAutoTransition(envelopeValue) {
|
|
7449
|
-
const env = process.env.HARNESSED_AUTO_TRANSITION;
|
|
7450
|
-
if (env === "true") return true;
|
|
7451
|
-
if (env === "false") return false;
|
|
7452
|
-
return envelopeValue ?? true;
|
|
7453
|
-
}
|
|
7454
7859
|
function registerNext(program2) {
|
|
7455
7860
|
program2.command("next").description(
|
|
7456
7861
|
"Print the deterministic next-step contract (NEXT: auto|manual|done) for the active workflow"
|
|
7457
7862
|
).action(async () => {
|
|
7458
7863
|
const { readCurrentWorkflow: readCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7459
|
-
const { resolveNext: resolveNext2 } = await Promise.resolve().then(() => (init_nextStep(), nextStep_exports));
|
|
7864
|
+
const { resolveNext: resolveNext2, resolveAutoTransition: resolveAutoTransition2 } = await Promise.resolve().then(() => (init_nextStep(), nextStep_exports));
|
|
7460
7865
|
const current = await readCurrentWorkflow2();
|
|
7461
7866
|
const ledger = current?.sub_progress ?? [];
|
|
7462
|
-
const auto =
|
|
7867
|
+
const auto = resolveAutoTransition2(current?.auto_transition);
|
|
7463
7868
|
const step = resolveNext2(ledger, auto);
|
|
7464
7869
|
console.log(`NEXT: ${step.next}`);
|
|
7465
7870
|
if (step.sub) console.log(`SUB: ${step.sub}`);
|
|
@@ -7743,6 +8148,37 @@ function registerResume(program2) {
|
|
|
7743
8148
|
process.exit(0);
|
|
7744
8149
|
});
|
|
7745
8150
|
}
|
|
8151
|
+
|
|
8152
|
+
// src/cli/retro.ts
|
|
8153
|
+
init_retroMeta();
|
|
8154
|
+
async function runRetroDone(now) {
|
|
8155
|
+
const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow2, writeCurrentWorkflow: writeCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
8156
|
+
const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
|
|
8157
|
+
const key = repoKey2();
|
|
8158
|
+
await mutateStore2((store) => {
|
|
8159
|
+
const meta = { ...store.retro_meta ?? {} };
|
|
8160
|
+
meta[key] = resetRetro(now);
|
|
8161
|
+
return { ...store, retro_meta: meta };
|
|
8162
|
+
});
|
|
8163
|
+
const wf = await readCurrentWorkflow2();
|
|
8164
|
+
if (wf?.retro_due) await writeCurrentWorkflow2({ ...wf, retro_due: false });
|
|
8165
|
+
return { reset: true };
|
|
8166
|
+
}
|
|
8167
|
+
function registerRetro(program2) {
|
|
8168
|
+
program2.command("retro").description("Retro cadence: --done resets the phase counter after running /retro").option(
|
|
8169
|
+
"--done",
|
|
8170
|
+
"mark a retro as done \u2014 reset the phase counter + clear the RETRO-DUE reminder"
|
|
8171
|
+
).action(async (opts) => {
|
|
8172
|
+
if (!opts.done) {
|
|
8173
|
+
console.error("[harnessed] retro: nothing to do \u2014 pass --done after running /retro");
|
|
8174
|
+
process.exit(1);
|
|
8175
|
+
return;
|
|
8176
|
+
}
|
|
8177
|
+
await runRetroDone((/* @__PURE__ */ new Date()).toISOString());
|
|
8178
|
+
console.log("[harnessed] retro recorded \u2014 phase counter reset, RETRO-DUE reminder cleared");
|
|
8179
|
+
process.exit(0);
|
|
8180
|
+
});
|
|
8181
|
+
}
|
|
7746
8182
|
init_backup();
|
|
7747
8183
|
function normalizeEol(buf, eol) {
|
|
7748
8184
|
const lf = buf.toString("utf8").replace(/\r\n/g, "\n");
|
|
@@ -7814,9 +8250,13 @@ ${t("rollback.metadata_unreadable.fix")}`
|
|
|
7814
8250
|
|
|
7815
8251
|
// src/cli.ts
|
|
7816
8252
|
init_run2();
|
|
8253
|
+
init_platform();
|
|
8254
|
+
|
|
8255
|
+
// src/cli/lib/capabilityResolver.ts
|
|
8256
|
+
init_platform();
|
|
7817
8257
|
function readInstalledPlugins(homedirOverride) {
|
|
7818
|
-
const
|
|
7819
|
-
|
|
8258
|
+
const path = getPluginsRegistry(homedirOverride);
|
|
8259
|
+
if (path === null) return /* @__PURE__ */ new Set();
|
|
7820
8260
|
let raw;
|
|
7821
8261
|
try {
|
|
7822
8262
|
raw = readFileSync(path, "utf8");
|
|
@@ -7841,8 +8281,7 @@ function readInstalledPlugins(homedirOverride) {
|
|
|
7841
8281
|
return out;
|
|
7842
8282
|
}
|
|
7843
8283
|
function readInstalledUserSkills(homedirOverride) {
|
|
7844
|
-
const
|
|
7845
|
-
const skillsRoot = join(home, ".claude", "skills");
|
|
8284
|
+
const skillsRoot = getSkillsDir(homedirOverride);
|
|
7846
8285
|
try {
|
|
7847
8286
|
const entries = readdirSync(skillsRoot, { withFileTypes: true });
|
|
7848
8287
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -7919,55 +8358,57 @@ function renderSkillBody(body, capabilities, installedPlugins, installedUserSkil
|
|
|
7919
8358
|
}
|
|
7920
8359
|
|
|
7921
8360
|
// src/cli/lib/enableAgentTeamsInSettings.ts
|
|
8361
|
+
init_platform();
|
|
8362
|
+
|
|
8363
|
+
// src/cli/lib/settingsWriter.ts
|
|
7922
8364
|
init_harnessedRoot();
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
async function enableAgentTeamsInSettings() {
|
|
7927
|
-
const path = settingsPath();
|
|
8365
|
+
init_platform();
|
|
8366
|
+
async function mergeSettingsEnvKey(key, value, opts = {}) {
|
|
8367
|
+
const path = getSettingsPath();
|
|
7928
8368
|
let raw;
|
|
7929
8369
|
try {
|
|
7930
8370
|
raw = await readFile(path, "utf8");
|
|
7931
8371
|
} catch (err2) {
|
|
7932
8372
|
const code = err2.code;
|
|
7933
8373
|
if (code !== "ENOENT") {
|
|
7934
|
-
return {
|
|
8374
|
+
return { outcome: "warn", message: `read ${path} failed: ${err2.message}` };
|
|
7935
8375
|
}
|
|
7936
|
-
return
|
|
8376
|
+
return createFresh(path, key, value);
|
|
7937
8377
|
}
|
|
7938
8378
|
let data;
|
|
7939
8379
|
try {
|
|
7940
8380
|
const parsed = JSON.parse(raw);
|
|
7941
8381
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
7942
|
-
return {
|
|
8382
|
+
return { outcome: "warn", message: `${path} is not a JSON object` };
|
|
7943
8383
|
}
|
|
7944
8384
|
data = parsed;
|
|
7945
8385
|
} catch (err2) {
|
|
7946
|
-
return {
|
|
8386
|
+
return { outcome: "warn", message: `${path} malformed JSON: ${err2.message}` };
|
|
7947
8387
|
}
|
|
7948
8388
|
const env = data.env ?? {};
|
|
7949
|
-
|
|
7950
|
-
|
|
8389
|
+
const existing = env[key];
|
|
8390
|
+
if (typeof existing === "string" && existing.length > 0 && opts.skipIfPresent?.(existing) === true) {
|
|
8391
|
+
return { outcome: "already", path, existing };
|
|
7951
8392
|
}
|
|
7952
|
-
const
|
|
7953
|
-
if (
|
|
7954
|
-
data.env = { ...env,
|
|
8393
|
+
const backup2 = await backupOriginal(raw);
|
|
8394
|
+
if (backup2.status === "warn") return { outcome: "warn", message: backup2.message };
|
|
8395
|
+
data.env = { ...env, [key]: value };
|
|
7955
8396
|
const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
|
|
7956
8397
|
`);
|
|
7957
|
-
if (writeErr) return {
|
|
7958
|
-
return {
|
|
8398
|
+
if (writeErr) return { outcome: "warn", message: writeErr };
|
|
8399
|
+
return { outcome: "merged", path, backupPath: backup2.path };
|
|
7959
8400
|
}
|
|
7960
|
-
async function
|
|
7961
|
-
const data = { env: {
|
|
8401
|
+
async function createFresh(path, key, value) {
|
|
8402
|
+
const data = { env: { [key]: value } };
|
|
7962
8403
|
try {
|
|
7963
|
-
await mkdir(
|
|
8404
|
+
await mkdir(dirname(path), { recursive: true });
|
|
7964
8405
|
} catch (err2) {
|
|
7965
|
-
return {
|
|
8406
|
+
return { outcome: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
|
|
7966
8407
|
}
|
|
7967
8408
|
const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
|
|
7968
8409
|
`);
|
|
7969
|
-
if (writeErr) return {
|
|
7970
|
-
return {
|
|
8410
|
+
if (writeErr) return { outcome: "warn", message: writeErr };
|
|
8411
|
+
return { outcome: "created", path };
|
|
7971
8412
|
}
|
|
7972
8413
|
async function backupOriginal(raw) {
|
|
7973
8414
|
const backupRoot = harnessedSubdir("backups");
|
|
@@ -7992,11 +8433,34 @@ async function atomicWrite(path, content) {
|
|
|
7992
8433
|
}
|
|
7993
8434
|
}
|
|
7994
8435
|
|
|
7995
|
-
// src/cli/lib/
|
|
7996
|
-
|
|
7997
|
-
function
|
|
7998
|
-
|
|
8436
|
+
// src/cli/lib/enableAgentTeamsInSettings.ts
|
|
8437
|
+
var ENV_KEY = "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS";
|
|
8438
|
+
async function enableAgentTeamsInSettings() {
|
|
8439
|
+
const platform = detectPlatform();
|
|
8440
|
+
if (!platform.supportsEnvKeyWrite) {
|
|
8441
|
+
return {
|
|
8442
|
+
status: "warn",
|
|
8443
|
+
message: `platform '${platform.id}' does not support env-key settings writes (capability-absent) \u2014 ${ENV_KEY} skipped`
|
|
8444
|
+
};
|
|
8445
|
+
}
|
|
8446
|
+
const r = await mergeSettingsEnvKey(ENV_KEY, "1", {
|
|
8447
|
+
skipIfPresent: (existing) => existing === "1"
|
|
8448
|
+
});
|
|
8449
|
+
switch (r.outcome) {
|
|
8450
|
+
case "created":
|
|
8451
|
+
return { status: "created", path: r.path };
|
|
8452
|
+
case "already":
|
|
8453
|
+
return { status: "already-enabled", path: r.path };
|
|
8454
|
+
case "merged":
|
|
8455
|
+
return { status: "enabled", path: r.path, backupPath: r.backupPath };
|
|
8456
|
+
case "warn":
|
|
8457
|
+
return { status: "warn", message: r.message };
|
|
8458
|
+
}
|
|
7999
8459
|
}
|
|
8460
|
+
|
|
8461
|
+
// src/cli/lib/enableUserLangInSettings.ts
|
|
8462
|
+
init_platform();
|
|
8463
|
+
var ENV_KEY2 = "HARNESSED_USER_LANG";
|
|
8000
8464
|
function detectUserLang(override) {
|
|
8001
8465
|
if (override) {
|
|
8002
8466
|
if (/^zh([^a-z]|$)/i.test(override)) return "zh-Hans";
|
|
@@ -8014,73 +8478,26 @@ function safeIntlLocale2() {
|
|
|
8014
8478
|
}
|
|
8015
8479
|
}
|
|
8016
8480
|
async function enableUserLangInSettings(override) {
|
|
8017
|
-
const
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
const code = err2.code;
|
|
8024
|
-
if (code !== "ENOENT") {
|
|
8025
|
-
return { status: "warn", message: `read ${path} failed: ${err2.message}` };
|
|
8026
|
-
}
|
|
8027
|
-
return createFreshSettings2(path, detected);
|
|
8028
|
-
}
|
|
8029
|
-
let data;
|
|
8030
|
-
try {
|
|
8031
|
-
const parsed = JSON.parse(raw);
|
|
8032
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8033
|
-
return { status: "warn", message: `${path} is not a JSON object` };
|
|
8034
|
-
}
|
|
8035
|
-
data = parsed;
|
|
8036
|
-
} catch (err2) {
|
|
8037
|
-
return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
|
|
8038
|
-
}
|
|
8039
|
-
const env = data.env ?? {};
|
|
8040
|
-
const existing = env.HARNESSED_USER_LANG;
|
|
8041
|
-
if (typeof existing === "string" && existing.length > 0 && override === void 0) {
|
|
8042
|
-
return { status: "already-set", path, existing };
|
|
8043
|
-
}
|
|
8044
|
-
const backupPath = await backupOriginal2(raw);
|
|
8045
|
-
if (backupPath.status === "warn") return backupPath;
|
|
8046
|
-
data.env = { ...env, HARNESSED_USER_LANG: detected };
|
|
8047
|
-
const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
|
|
8048
|
-
`);
|
|
8049
|
-
if (writeErr) return { status: "warn", message: writeErr };
|
|
8050
|
-
return { status: "enabled", path, backupPath: backupPath.path, detected };
|
|
8051
|
-
}
|
|
8052
|
-
async function createFreshSettings2(path, detected) {
|
|
8053
|
-
const data = { env: { HARNESSED_USER_LANG: detected } };
|
|
8054
|
-
try {
|
|
8055
|
-
await mkdir(join(homedir(), ".claude"), { recursive: true });
|
|
8056
|
-
} catch (err2) {
|
|
8057
|
-
return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
|
|
8058
|
-
}
|
|
8059
|
-
const writeErr = await atomicWrite2(path, `${JSON.stringify(data, null, 2)}
|
|
8060
|
-
`);
|
|
8061
|
-
if (writeErr) return { status: "warn", message: writeErr };
|
|
8062
|
-
return { status: "created", path, detected };
|
|
8063
|
-
}
|
|
8064
|
-
async function backupOriginal2(raw) {
|
|
8065
|
-
const backupRoot = harnessedSubdir("backups");
|
|
8066
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
8067
|
-
const backupPath = join(backupRoot, `settings.json.${ts}.bak`);
|
|
8068
|
-
try {
|
|
8069
|
-
await mkdir(backupRoot, { recursive: true });
|
|
8070
|
-
await writeFile(backupPath, raw, "utf8");
|
|
8071
|
-
return { status: "ok", path: backupPath };
|
|
8072
|
-
} catch (err2) {
|
|
8073
|
-
return { status: "warn", message: `backup ${backupPath} failed: ${err2.message}` };
|
|
8481
|
+
const platform = detectPlatform();
|
|
8482
|
+
if (!platform.supportsEnvKeyWrite) {
|
|
8483
|
+
return {
|
|
8484
|
+
status: "warn",
|
|
8485
|
+
message: `platform '${platform.id}' does not support env-key settings writes (capability-absent) \u2014 ${ENV_KEY2} skipped`
|
|
8486
|
+
};
|
|
8074
8487
|
}
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8488
|
+
const detected = detectUserLang(override);
|
|
8489
|
+
const r = await mergeSettingsEnvKey(ENV_KEY2, detected, {
|
|
8490
|
+
skipIfPresent: () => override === void 0
|
|
8491
|
+
});
|
|
8492
|
+
switch (r.outcome) {
|
|
8493
|
+
case "created":
|
|
8494
|
+
return { status: "created", path: r.path, detected };
|
|
8495
|
+
case "already":
|
|
8496
|
+
return { status: "already-set", path: r.path, existing: r.existing };
|
|
8497
|
+
case "merged":
|
|
8498
|
+
return { status: "enabled", path: r.path, backupPath: r.backupPath, detected };
|
|
8499
|
+
case "warn":
|
|
8500
|
+
return { status: "warn", message: r.message };
|
|
8084
8501
|
}
|
|
8085
8502
|
}
|
|
8086
8503
|
|
|
@@ -8140,8 +8557,8 @@ async function renderAllSkills(skillNames, skillsBase, workflowsDir, homedirOver
|
|
|
8140
8557
|
]
|
|
8141
8558
|
};
|
|
8142
8559
|
}
|
|
8143
|
-
const installedPlugins = readInstalledPlugins();
|
|
8144
|
-
const installedUserSkills = readInstalledUserSkills();
|
|
8560
|
+
const installedPlugins = readInstalledPlugins(homedirOverride);
|
|
8561
|
+
const installedUserSkills = readInstalledUserSkills(homedirOverride);
|
|
8145
8562
|
const results = [];
|
|
8146
8563
|
const warningSet = /* @__PURE__ */ new Set();
|
|
8147
8564
|
for (const name of skillNames) {
|
|
@@ -8304,6 +8721,23 @@ async function runStepBInstall(manifestPaths, runOpts = {}) {
|
|
|
8304
8721
|
}
|
|
8305
8722
|
|
|
8306
8723
|
// src/cli/setup.ts
|
|
8724
|
+
var KNOWN_PLATFORMS = ["claude", "codex"];
|
|
8725
|
+
async function applyPlatformOption(platform) {
|
|
8726
|
+
if (!KNOWN_PLATFORMS.includes(platform)) {
|
|
8727
|
+
console.error(
|
|
8728
|
+
`--platform: unknown id '${platform}' (expected one of: ${KNOWN_PLATFORMS.join(" | ")})`
|
|
8729
|
+
);
|
|
8730
|
+
process.exit(1);
|
|
8731
|
+
}
|
|
8732
|
+
process.env.HARNESSED_PLATFORM = platform;
|
|
8733
|
+
const stateRoot = detectPlatform().stateRoot;
|
|
8734
|
+
try {
|
|
8735
|
+
await mkdir(stateRoot, { recursive: true });
|
|
8736
|
+
await writeFile(join(stateRoot, ".platform"), platform, "utf8");
|
|
8737
|
+
} catch (e) {
|
|
8738
|
+
console.warn(` [--platform] could not persist .platform pin (${e.message})`);
|
|
8739
|
+
}
|
|
8740
|
+
}
|
|
8307
8741
|
async function listBaseManifests2(pkgRoot) {
|
|
8308
8742
|
const out = [];
|
|
8309
8743
|
for (const d of ["manifests/tools", "manifests/skill-packs"]) {
|
|
@@ -8356,14 +8790,15 @@ function registerSetup(program2) {
|
|
|
8356
8790
|
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option(
|
|
8357
8791
|
"--user-lang <code>",
|
|
8358
8792
|
"override detected OS locale for env.HARNESSED_USER_LANG (en | zh-Hans / zh-CN / zh-TW)"
|
|
8359
|
-
).option("--non-interactive", "skip all confirm prompts (CI / scripted setup)").option("--no-auto-install", "do not prompt to auto-install missing plugins (advisory only)").option(
|
|
8793
|
+
).option("--platform <id>", "target harness platform (claude | codex); default: auto-detect").option("--non-interactive", "skip all confirm prompts (CI / scripted setup)").option("--no-auto-install", "do not prompt to auto-install missing plugins (advisory only)").option(
|
|
8360
8794
|
"--update-installed",
|
|
8361
8795
|
"force re-install already-installed plugins (excludes MCP servers); default: skip if installed"
|
|
8362
8796
|
).action(async (raw) => {
|
|
8363
8797
|
const dryRun = raw.dryRun === true;
|
|
8798
|
+
if (raw.platform !== void 0) await applyPlatformOption(raw.platform);
|
|
8364
8799
|
const pkgRoot = getPackageRoot();
|
|
8365
8800
|
const workflowsDir = resolve(pkgRoot, "workflows");
|
|
8366
|
-
const skillsBase =
|
|
8801
|
+
const skillsBase = getSkillsDir();
|
|
8367
8802
|
await warnIfAgentTeamsMissing();
|
|
8368
8803
|
let entries;
|
|
8369
8804
|
try {
|
|
@@ -8407,7 +8842,7 @@ function registerSetup(program2) {
|
|
|
8407
8842
|
console.warn(` - ${w}`);
|
|
8408
8843
|
}
|
|
8409
8844
|
}
|
|
8410
|
-
const commandsBase =
|
|
8845
|
+
const commandsBase = getCommandsDir();
|
|
8411
8846
|
try {
|
|
8412
8847
|
await mkdir(commandsBase, { recursive: true });
|
|
8413
8848
|
} catch (e) {
|
|
@@ -8502,116 +8937,6 @@ Force-update pass complete: ${b2.installed.length} installed / ${b2.alreadyInsta
|
|
|
8502
8937
|
process.exit(0);
|
|
8503
8938
|
});
|
|
8504
8939
|
}
|
|
8505
|
-
|
|
8506
|
-
// src/cli/status.ts
|
|
8507
|
-
init_evidence();
|
|
8508
|
-
init_ledger();
|
|
8509
|
-
init_state();
|
|
8510
|
-
init_harnessedRoot();
|
|
8511
|
-
init_state2();
|
|
8512
|
-
function statusMarker(status) {
|
|
8513
|
-
switch (status) {
|
|
8514
|
-
case "done":
|
|
8515
|
-
return "\u2705 done";
|
|
8516
|
-
case "pending":
|
|
8517
|
-
return "\u23F3 pending";
|
|
8518
|
-
case "failed":
|
|
8519
|
-
return "\u2717 failed";
|
|
8520
|
-
case "skipped":
|
|
8521
|
-
return "\u2B1C skipped";
|
|
8522
|
-
case "rejected":
|
|
8523
|
-
return "\u{1F6AB} rejected";
|
|
8524
|
-
}
|
|
8525
|
-
}
|
|
8526
|
-
function evidenceLabel(entry) {
|
|
8527
|
-
switch (entry.evidence_status) {
|
|
8528
|
-
case "verified":
|
|
8529
|
-
return "evidence: verified";
|
|
8530
|
-
case "overridden":
|
|
8531
|
-
return "evidence: overridden (--force)";
|
|
8532
|
-
default:
|
|
8533
|
-
return "evidence: none_declared";
|
|
8534
|
-
}
|
|
8535
|
-
}
|
|
8536
|
-
async function buildRecoverLines(workflow, ledger) {
|
|
8537
|
-
const lines = [];
|
|
8538
|
-
if (!workflow || ledger.length === 0) {
|
|
8539
|
-
lines.push("no ledger \u2014 run gates + start");
|
|
8540
|
-
return lines;
|
|
8541
|
-
}
|
|
8542
|
-
lines.push(`workflow: ${workflow.phase} (${workflow.status}) started ${workflow.started_at}`);
|
|
8543
|
-
const next = nextPending(ledger);
|
|
8544
|
-
for (const e of ledger) {
|
|
8545
|
-
const marker = statusMarker(e.status);
|
|
8546
|
-
let suffix = "";
|
|
8547
|
-
if (e.status === "skipped") {
|
|
8548
|
-
suffix = e.reason ? ` (skipped: ${e.reason})` : "";
|
|
8549
|
-
} else if (e.status === "done") {
|
|
8550
|
-
suffix = ` ${evidenceLabel(e)}`;
|
|
8551
|
-
}
|
|
8552
|
-
const arrow = e.sub === next ? " \u2190 next" : "";
|
|
8553
|
-
lines.push(` ${e.sub} ${marker}${suffix}${arrow}`);
|
|
8554
|
-
}
|
|
8555
|
-
if (next) {
|
|
8556
|
-
lines.push(`\u2192 next: harnessed prompt ${next}`);
|
|
8557
|
-
} else {
|
|
8558
|
-
lines.push("\u2192 all subs resolved (no pending work)");
|
|
8559
|
-
}
|
|
8560
|
-
for (const e of ledger) {
|
|
8561
|
-
if (!e.evidence || e.evidence.length === 0) continue;
|
|
8562
|
-
const drift = await detectDrift(e.evidence);
|
|
8563
|
-
for (const d of drift) {
|
|
8564
|
-
const now = d.now === "" ? "missing" : `${d.now.slice(0, 7)}\u2026`;
|
|
8565
|
-
lines.push(`\u26A0 drift: ${d.path} sha256 changed (was ${d.was.slice(0, 7)}\u2026, now ${now})`);
|
|
8566
|
-
}
|
|
8567
|
-
}
|
|
8568
|
-
return lines;
|
|
8569
|
-
}
|
|
8570
|
-
function registerStatus(program2) {
|
|
8571
|
-
program2.command("status").description("Show installed upstreams (from <harnessed-root>/state.json)").option("--recover", "structured recovery view of the active workflow ledger (v5.0 Spec 1 B)").action(async (opts) => {
|
|
8572
|
-
if (opts.recover) {
|
|
8573
|
-
const wf = await readCurrentWorkflow();
|
|
8574
|
-
const ledger = wf?.sub_progress ?? [];
|
|
8575
|
-
const lines = await buildRecoverLines(wf, ledger);
|
|
8576
|
-
for (const l of lines) console.log(l);
|
|
8577
|
-
return;
|
|
8578
|
-
}
|
|
8579
|
-
const state = await readState(process.cwd());
|
|
8580
|
-
const names = Object.keys(state.installed).sort();
|
|
8581
|
-
if (names.length === 0) {
|
|
8582
|
-
console.log(t("status.no_installs", { path: harnessedFile("state.json") }));
|
|
8583
|
-
} else {
|
|
8584
|
-
for (const n of names) {
|
|
8585
|
-
const e = state.installed[n];
|
|
8586
|
-
if (!e) continue;
|
|
8587
|
-
console.log(`${n} @ ${e.version} (installed ${e.installedAt})`);
|
|
8588
|
-
}
|
|
8589
|
-
console.log(t("status.summary_installs", { count: names.length }));
|
|
8590
|
-
}
|
|
8591
|
-
const lockPath = harnessedFile(".lock");
|
|
8592
|
-
try {
|
|
8593
|
-
const isLocked = await lockfile.check(getHarnessedRoot(), {
|
|
8594
|
-
lockfilePath: lockPath,
|
|
8595
|
-
stale: 1e4
|
|
8596
|
-
});
|
|
8597
|
-
if (isLocked) {
|
|
8598
|
-
const s = await stat(lockPath);
|
|
8599
|
-
const ageMs = Date.now() - s.mtime.getTime();
|
|
8600
|
-
const stale = ageMs > 1e4;
|
|
8601
|
-
console.log(
|
|
8602
|
-
t("status.lock_held", {
|
|
8603
|
-
since: s.mtime.toISOString(),
|
|
8604
|
-
staleSuffix: stale ? t("status.lock_held.stale_suffix") : ""
|
|
8605
|
-
})
|
|
8606
|
-
);
|
|
8607
|
-
console.log(t("status.lock_release_hint", { path: lockPath }));
|
|
8608
|
-
} else {
|
|
8609
|
-
console.log(t("status.lock_free"));
|
|
8610
|
-
}
|
|
8611
|
-
} catch {
|
|
8612
|
-
}
|
|
8613
|
-
});
|
|
8614
|
-
}
|
|
8615
8940
|
init_harnessedRoot();
|
|
8616
8941
|
init_path_guard();
|
|
8617
8942
|
init_validate();
|
|
@@ -8630,10 +8955,10 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
8630
8955
|
}
|
|
8631
8956
|
const abort = dryRunGate(ctx);
|
|
8632
8957
|
if (abort) return abort;
|
|
8633
|
-
const
|
|
8958
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
8634
8959
|
let existing;
|
|
8635
8960
|
try {
|
|
8636
|
-
existing = await readFile(
|
|
8961
|
+
existing = await readFile(settingsPath, "utf8");
|
|
8637
8962
|
} catch {
|
|
8638
8963
|
return { ok: true, removedPaths: [] };
|
|
8639
8964
|
}
|
|
@@ -8662,8 +8987,8 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
8662
8987
|
if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
|
|
8663
8988
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
8664
8989
|
`;
|
|
8665
|
-
await writeFile(
|
|
8666
|
-
return { ok: true, removedPaths: [
|
|
8990
|
+
await writeFile(settingsPath, newText);
|
|
8991
|
+
return { ok: true, removedPaths: [settingsPath] };
|
|
8667
8992
|
};
|
|
8668
8993
|
|
|
8669
8994
|
// src/uninstallers/ccPluginMarketplace.ts
|
|
@@ -8802,7 +9127,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
8802
9127
|
const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
|
|
8803
9128
|
const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
|
|
8804
9129
|
const isWin = process.platform === "win32";
|
|
8805
|
-
const result = await new Promise((
|
|
9130
|
+
const result = await new Promise((resolve18) => {
|
|
8806
9131
|
const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
|
|
8807
9132
|
let stderr = "";
|
|
8808
9133
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -8810,15 +9135,15 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
8810
9135
|
});
|
|
8811
9136
|
const timer = setTimeout(() => {
|
|
8812
9137
|
child.kill("SIGKILL");
|
|
8813
|
-
|
|
9138
|
+
resolve18({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
8814
9139
|
}, 3e4);
|
|
8815
9140
|
child.on("error", (e) => {
|
|
8816
9141
|
clearTimeout(timer);
|
|
8817
|
-
|
|
9142
|
+
resolve18({ exitCode: -1, stderr: e.message });
|
|
8818
9143
|
});
|
|
8819
9144
|
child.on("close", (code) => {
|
|
8820
9145
|
clearTimeout(timer);
|
|
8821
|
-
|
|
9146
|
+
resolve18({ exitCode: code ?? -1, stderr });
|
|
8822
9147
|
});
|
|
8823
9148
|
});
|
|
8824
9149
|
if (result.exitCode !== 0) {
|
|
@@ -8884,9 +9209,9 @@ async function discoverCommandFiles(commandsDir) {
|
|
|
8884
9209
|
}
|
|
8885
9210
|
return owned;
|
|
8886
9211
|
}
|
|
8887
|
-
async function checkSettingsEnv(
|
|
9212
|
+
async function checkSettingsEnv(settingsPath) {
|
|
8888
9213
|
try {
|
|
8889
|
-
const raw = await readFile(
|
|
9214
|
+
const raw = await readFile(settingsPath, "utf8");
|
|
8890
9215
|
const data = JSON.parse(raw);
|
|
8891
9216
|
const env = data.env ?? {};
|
|
8892
9217
|
return {
|
|
@@ -8897,8 +9222,8 @@ async function checkSettingsEnv(settingsPath3) {
|
|
|
8897
9222
|
return { hasAgentTeams: false, hasUserLang: false };
|
|
8898
9223
|
}
|
|
8899
9224
|
}
|
|
8900
|
-
async function removeSettingsEnv(
|
|
8901
|
-
const raw = await readFile(
|
|
9225
|
+
async function removeSettingsEnv(settingsPath) {
|
|
9226
|
+
const raw = await readFile(settingsPath, "utf8");
|
|
8902
9227
|
const data = JSON.parse(raw);
|
|
8903
9228
|
const env = data.env ?? {};
|
|
8904
9229
|
let changed = false;
|
|
@@ -8913,14 +9238,14 @@ async function removeSettingsEnv(settingsPath3) {
|
|
|
8913
9238
|
if (!changed) return false;
|
|
8914
9239
|
if (Object.keys(env).length === 0) delete data.env;
|
|
8915
9240
|
else data.env = env;
|
|
8916
|
-
await writeFile(
|
|
9241
|
+
await writeFile(settingsPath, `${JSON.stringify(data, null, 2)}
|
|
8917
9242
|
`, "utf8");
|
|
8918
9243
|
return true;
|
|
8919
9244
|
}
|
|
8920
9245
|
async function runUnifiedUninstall(home, dryRun) {
|
|
8921
9246
|
const commandsDir = join(home, ".claude", "commands");
|
|
8922
9247
|
const skillsDir = join(home, ".claude", "skills");
|
|
8923
|
-
const
|
|
9248
|
+
const settingsPath = join(home, ".claude", "settings.json");
|
|
8924
9249
|
const harnessedRoot = getHarnessedRoot();
|
|
8925
9250
|
const pkgRoot = getPackageRoot();
|
|
8926
9251
|
let workflowNames = [];
|
|
@@ -8931,7 +9256,7 @@ async function runUnifiedUninstall(home, dryRun) {
|
|
|
8931
9256
|
} catch {
|
|
8932
9257
|
}
|
|
8933
9258
|
const commandFiles = await discoverCommandFiles(commandsDir);
|
|
8934
|
-
const settingsEnv = await checkSettingsEnv(
|
|
9259
|
+
const settingsEnv = await checkSettingsEnv(settingsPath);
|
|
8935
9260
|
const hasSettingsChanges = settingsEnv.hasAgentTeams || settingsEnv.hasUserLang;
|
|
8936
9261
|
const skillDirs = [];
|
|
8937
9262
|
for (const name of workflowNames) {
|
|
@@ -8989,9 +9314,9 @@ async function runUnifiedUninstall(home, dryRun) {
|
|
|
8989
9314
|
let removedSettings = false;
|
|
8990
9315
|
if (hasSettingsChanges) {
|
|
8991
9316
|
try {
|
|
8992
|
-
removedSettings = await removeSettingsEnv(
|
|
9317
|
+
removedSettings = await removeSettingsEnv(settingsPath);
|
|
8993
9318
|
} catch (e) {
|
|
8994
|
-
failures.push(`${
|
|
9319
|
+
failures.push(`${settingsPath}: ${e.message}`);
|
|
8995
9320
|
}
|
|
8996
9321
|
}
|
|
8997
9322
|
const normalizedRoot = resolve(harnessedRoot);
|
|
@@ -9238,6 +9563,12 @@ registerWorkflows(program);
|
|
|
9238
9563
|
registerLearn(program);
|
|
9239
9564
|
registerUpdate(program);
|
|
9240
9565
|
registerReleasePreflight(program);
|
|
9241
|
-
program
|
|
9566
|
+
registerRetro(program);
|
|
9567
|
+
var bare = parseBareInvocation(process.argv.slice(2));
|
|
9568
|
+
if (bare.here) {
|
|
9569
|
+
void runHere({ json: bare.json });
|
|
9570
|
+
} else {
|
|
9571
|
+
program.parse(process.argv);
|
|
9572
|
+
}
|
|
9242
9573
|
//# sourceMappingURL=cli.mjs.map
|
|
9243
9574
|
//# sourceMappingURL=cli.mjs.map
|