harnessed 4.5.1 → 4.6.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 +1 -0
- package/bin/harnessed-inject-state.mjs +11 -0
- package/dist/cli.mjs +179 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -416,6 +416,7 @@ planning-with-files /plan (cross-cutting tool) → write artifacts to .planning/
|
|
|
416
416
|
| `harnessed doctor` | 14-check health check (Node / MCP / jq / Win bash / routing / token budget / mattpocock / CodeGraph / update-available, etc.) |
|
|
417
417
|
| `harnessed update [--check\|--upstreams\|--migration-report]` | Self-update (`npm i -g harnessed@latest`); `--check` reports the latest version; `--upstreams` re-runs the base manifests; `--migration-report` is a read-only stale-state inventory |
|
|
418
418
|
| `harnessed release-preflight` | Read-only release-readiness gate (CHANGELOG `[Unreleased]` / version / git-clean / tag-absent); exits 1 if not ready. The Ship-stage gate. |
|
|
419
|
+
| `harnessed retro --done` | Reset the retro-reminder phase counter after running `/retro` (clears the per-turn RETRO-DUE nudge). |
|
|
419
420
|
| `harnessed install <name>` | Install an upstream manifest |
|
|
420
421
|
| `harnessed uninstall [name]` | Reverse uninstall |
|
|
421
422
|
| `harnessed backup` | Snapshot backup management |
|
|
@@ -65,6 +65,17 @@ function workflowStateBlock(wf) {
|
|
|
65
65
|
`BREAK-LOOP: sub '${e.sub}' failed ${e.fail_count}x — stop retrying, run break-loop skill`,
|
|
66
66
|
)
|
|
67
67
|
}
|
|
68
|
+
// Phase 22 — smart-reminder lines from envelope flags (parity with injectState.ts).
|
|
69
|
+
if (wf.ship_ready) {
|
|
70
|
+
lines.push(
|
|
71
|
+
`SHIP-READY: ${wf.ship_commits ?? 0} commit(s) since the last release tag — consider shipping (harnessed release-preflight, then /ship)`,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
if (wf.retro_due) {
|
|
75
|
+
lines.push(
|
|
76
|
+
'RETRO-DUE: enough phases completed since the last retro — run /retro, then `harnessed retro --done`',
|
|
77
|
+
)
|
|
78
|
+
}
|
|
68
79
|
lines.push('</workflow-state>')
|
|
69
80
|
return lines.join('\n')
|
|
70
81
|
}
|
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.6.0",
|
|
42
42
|
description: "AI coding harness package manager + composition orchestrator",
|
|
43
43
|
type: "module",
|
|
44
44
|
license: "Apache-2.0",
|
|
@@ -1291,7 +1291,19 @@ var init_currentWorkflow_v1 = __esm({
|
|
|
1291
1291
|
},
|
|
1292
1292
|
{ additionalProperties: false }
|
|
1293
1293
|
)
|
|
1294
|
-
)
|
|
1294
|
+
),
|
|
1295
|
+
// Phase 22 — smart reminders. Additive-optional flags set by `checkpoint
|
|
1296
|
+
// complete` (allResolved) and read by the G4 inject twins (booleans only; the
|
|
1297
|
+
// bin computes neither git nor thresholds). Old files without them Value.Check-pass
|
|
1298
|
+
// → NO schemaVersion bump.
|
|
1299
|
+
// ship_ready — there are unshipped commits since the last vX.Y.Z tag (git-derived,
|
|
1300
|
+
// recomputed each completion; self-heals to false after a release).
|
|
1301
|
+
// ship_commits — commit count since that tag (feeds the SHIP-READY hint text).
|
|
1302
|
+
// retro_due — phases_since_retro (store sidecar) has crossed the threshold;
|
|
1303
|
+
// cleared by `harnessed retro --done`.
|
|
1304
|
+
ship_ready: Type.Optional(Type.Boolean()),
|
|
1305
|
+
ship_commits: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
1306
|
+
retro_due: Type.Optional(Type.Boolean())
|
|
1295
1307
|
},
|
|
1296
1308
|
{ additionalProperties: false }
|
|
1297
1309
|
);
|
|
@@ -1309,6 +1321,7 @@ var init_schema2 = __esm({
|
|
|
1309
1321
|
// src/checkpoint/workflowStore.ts
|
|
1310
1322
|
var workflowStore_exports = {};
|
|
1311
1323
|
__export(workflowStore_exports, {
|
|
1324
|
+
RetroMetaEntry: () => RetroMetaEntry,
|
|
1312
1325
|
WorkflowStoreV1: () => WorkflowStoreV1,
|
|
1313
1326
|
listWorkflows: () => listWorkflows,
|
|
1314
1327
|
readStoreRaw: () => readStoreRaw,
|
|
@@ -1369,17 +1382,26 @@ async function listWorkflows() {
|
|
|
1369
1382
|
status: wf.status
|
|
1370
1383
|
}));
|
|
1371
1384
|
}
|
|
1372
|
-
var WorkflowStoreV1;
|
|
1385
|
+
var RetroMetaEntry, WorkflowStoreV1;
|
|
1373
1386
|
var init_workflowStore = __esm({
|
|
1374
1387
|
"src/checkpoint/workflowStore.ts"() {
|
|
1375
1388
|
init_harnessedRoot();
|
|
1376
1389
|
init_schemaVersion();
|
|
1377
1390
|
init_atomicWrite();
|
|
1378
1391
|
init_currentWorkflow_v1();
|
|
1392
|
+
RetroMetaEntry = Type.Object(
|
|
1393
|
+
{
|
|
1394
|
+
phases_since_retro: Type.Integer({ minimum: 0 }),
|
|
1395
|
+
last_retro_at: Type.Optional(Type.String({ minLength: 1 }))
|
|
1396
|
+
},
|
|
1397
|
+
{ additionalProperties: false }
|
|
1398
|
+
);
|
|
1379
1399
|
WorkflowStoreV1 = Type.Object(
|
|
1380
1400
|
{
|
|
1381
1401
|
schemaVersion: Type.Literal(SCHEMA_VERSIONS.workflowStore),
|
|
1382
|
-
workflows: Type.Record(Type.String(), CurrentWorkflowV1)
|
|
1402
|
+
workflows: Type.Record(Type.String(), CurrentWorkflowV1),
|
|
1403
|
+
// Additive-optional (no schemaVersion bump — old stores Value.Check-pass).
|
|
1404
|
+
retro_meta: Type.Optional(Type.Record(Type.String(), RetroMetaEntry))
|
|
1383
1405
|
},
|
|
1384
1406
|
{ additionalProperties: false }
|
|
1385
1407
|
);
|
|
@@ -1393,6 +1415,7 @@ __export(state_exports, {
|
|
|
1393
1415
|
WorkflowStateError: () => WorkflowStateError,
|
|
1394
1416
|
activate: () => activate,
|
|
1395
1417
|
complete: () => complete,
|
|
1418
|
+
mutateStore: () => mutateStore,
|
|
1396
1419
|
mutateSubProgress: () => mutateSubProgress,
|
|
1397
1420
|
pause: () => pause,
|
|
1398
1421
|
readCurrentWorkflow: () => readCurrentWorkflow,
|
|
@@ -1452,6 +1475,12 @@ async function mutateSubProgress(fn) {
|
|
|
1452
1475
|
await writeCurrentWorkflowUnlocked({ ...s, sub_progress: next });
|
|
1453
1476
|
});
|
|
1454
1477
|
}
|
|
1478
|
+
async function mutateStore(fn) {
|
|
1479
|
+
await withLock(async () => {
|
|
1480
|
+
const store = await readStoreRaw();
|
|
1481
|
+
await writeStoreRaw(fn(store));
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1455
1484
|
async function activate(phase, checkpointPath = null) {
|
|
1456
1485
|
await writeCurrentWorkflow({
|
|
1457
1486
|
schemaVersion: SCHEMA_VERSIONS.currentWorkflow,
|
|
@@ -3801,6 +3830,91 @@ var init_compact = __esm({
|
|
|
3801
3830
|
}
|
|
3802
3831
|
});
|
|
3803
3832
|
|
|
3833
|
+
// src/checkpoint/shipReady.ts
|
|
3834
|
+
var shipReady_exports = {};
|
|
3835
|
+
__export(shipReady_exports, {
|
|
3836
|
+
collectShipReady: () => collectShipReady,
|
|
3837
|
+
defaultShipReady: () => defaultShipReady,
|
|
3838
|
+
pickLatestReleaseTag: () => pickLatestReleaseTag
|
|
3839
|
+
});
|
|
3840
|
+
function pickLatestReleaseTag(tags) {
|
|
3841
|
+
let best = null;
|
|
3842
|
+
for (const tag of tags) {
|
|
3843
|
+
const m = RELEASE_TAG.exec(tag.trim());
|
|
3844
|
+
if (!m) continue;
|
|
3845
|
+
const n = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
3846
|
+
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]) {
|
|
3847
|
+
best = { tag: tag.trim(), n };
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
return best?.tag ?? null;
|
|
3851
|
+
}
|
|
3852
|
+
function collectShipReady(tags, countSince) {
|
|
3853
|
+
const baseTag = pickLatestReleaseTag(tags);
|
|
3854
|
+
const commits = countSince(baseTag);
|
|
3855
|
+
return { ready: commits > 0, commits, baseTag };
|
|
3856
|
+
}
|
|
3857
|
+
function defaultShipReady(cwd) {
|
|
3858
|
+
let tags = [];
|
|
3859
|
+
try {
|
|
3860
|
+
tags = execFileSync("git", ["tag", "-l"], { cwd, encoding: "utf8" }).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
3861
|
+
} catch {
|
|
3862
|
+
return { ready: false, commits: 0, baseTag: null };
|
|
3863
|
+
}
|
|
3864
|
+
const countSince = (tag) => {
|
|
3865
|
+
try {
|
|
3866
|
+
const range = tag ? `${tag}..HEAD` : "HEAD";
|
|
3867
|
+
const out = execFileSync("git", ["rev-list", range, "--count"], {
|
|
3868
|
+
cwd,
|
|
3869
|
+
encoding: "utf8"
|
|
3870
|
+
}).trim();
|
|
3871
|
+
const n = Number(out);
|
|
3872
|
+
return Number.isFinite(n) ? n : 0;
|
|
3873
|
+
} catch {
|
|
3874
|
+
return 0;
|
|
3875
|
+
}
|
|
3876
|
+
};
|
|
3877
|
+
return collectShipReady(tags, countSince);
|
|
3878
|
+
}
|
|
3879
|
+
var RELEASE_TAG;
|
|
3880
|
+
var init_shipReady = __esm({
|
|
3881
|
+
"src/checkpoint/shipReady.ts"() {
|
|
3882
|
+
RELEASE_TAG = /^v(\d+)\.(\d+)\.(\d+)$/;
|
|
3883
|
+
}
|
|
3884
|
+
});
|
|
3885
|
+
|
|
3886
|
+
// src/checkpoint/retroMeta.ts
|
|
3887
|
+
var retroMeta_exports = {};
|
|
3888
|
+
__export(retroMeta_exports, {
|
|
3889
|
+
DEFAULT_RETRO_THRESHOLD: () => DEFAULT_RETRO_THRESHOLD,
|
|
3890
|
+
incrementPhases: () => incrementPhases,
|
|
3891
|
+
isRetroDue: () => isRetroDue,
|
|
3892
|
+
resetRetro: () => resetRetro,
|
|
3893
|
+
retroThreshold: () => retroThreshold
|
|
3894
|
+
});
|
|
3895
|
+
function retroThreshold(env) {
|
|
3896
|
+
const raw = env.HARNESSED_RETRO_PHASE_THRESHOLD;
|
|
3897
|
+
if (raw === void 0) return DEFAULT_RETRO_THRESHOLD;
|
|
3898
|
+
const n = Number(raw);
|
|
3899
|
+
return Number.isInteger(n) && n > 0 ? n : DEFAULT_RETRO_THRESHOLD;
|
|
3900
|
+
}
|
|
3901
|
+
function incrementPhases(entry) {
|
|
3902
|
+
if (!entry) return { phases_since_retro: 1 };
|
|
3903
|
+
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 };
|
|
3904
|
+
}
|
|
3905
|
+
function isRetroDue(count, threshold) {
|
|
3906
|
+
return count >= threshold;
|
|
3907
|
+
}
|
|
3908
|
+
function resetRetro(now) {
|
|
3909
|
+
return { phases_since_retro: 0, last_retro_at: now };
|
|
3910
|
+
}
|
|
3911
|
+
var DEFAULT_RETRO_THRESHOLD;
|
|
3912
|
+
var init_retroMeta = __esm({
|
|
3913
|
+
"src/checkpoint/retroMeta.ts"() {
|
|
3914
|
+
DEFAULT_RETRO_THRESHOLD = 5;
|
|
3915
|
+
}
|
|
3916
|
+
});
|
|
3917
|
+
|
|
3804
3918
|
// src/checkpoint/breakLoop.ts
|
|
3805
3919
|
var breakLoop_exports = {};
|
|
3806
3920
|
__export(breakLoop_exports, {
|
|
@@ -6999,6 +7113,35 @@ function registerCheckpoint(program2) {
|
|
|
6999
7113
|
);
|
|
7000
7114
|
}
|
|
7001
7115
|
}
|
|
7116
|
+
if (allResolved) {
|
|
7117
|
+
try {
|
|
7118
|
+
const { defaultShipReady: defaultShipReady2 } = await Promise.resolve().then(() => (init_shipReady(), shipReady_exports));
|
|
7119
|
+
const { incrementPhases: incrementPhases2, isRetroDue: isRetroDue2, retroThreshold: retroThreshold2 } = await Promise.resolve().then(() => (init_retroMeta(), retroMeta_exports));
|
|
7120
|
+
const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow4, writeCurrentWorkflow: writeCurrentWorkflow3 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7121
|
+
const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
|
|
7122
|
+
const ship = defaultShipReady2(process.cwd());
|
|
7123
|
+
const key = repoKey2();
|
|
7124
|
+
const threshold = retroThreshold2(process.env);
|
|
7125
|
+
let retroDue = false;
|
|
7126
|
+
await mutateStore2((store) => {
|
|
7127
|
+
const meta = { ...store.retro_meta ?? {} };
|
|
7128
|
+
const next = incrementPhases2(meta[key]);
|
|
7129
|
+
meta[key] = next;
|
|
7130
|
+
retroDue = isRetroDue2(next.phases_since_retro, threshold);
|
|
7131
|
+
return { ...store, retro_meta: meta };
|
|
7132
|
+
});
|
|
7133
|
+
const latest2 = await readCurrentWorkflow4();
|
|
7134
|
+
if (latest2) {
|
|
7135
|
+
await writeCurrentWorkflow3({
|
|
7136
|
+
...latest2,
|
|
7137
|
+
ship_ready: ship.ready,
|
|
7138
|
+
ship_commits: ship.commits,
|
|
7139
|
+
retro_due: retroDue
|
|
7140
|
+
});
|
|
7141
|
+
}
|
|
7142
|
+
} catch {
|
|
7143
|
+
}
|
|
7144
|
+
}
|
|
7002
7145
|
console.log(`[harnessed] checkpoint complete: ${sub} (evidence: ${evidenceStatus})`);
|
|
7003
7146
|
process.exit(0);
|
|
7004
7147
|
return;
|
|
@@ -7743,6 +7886,37 @@ function registerResume(program2) {
|
|
|
7743
7886
|
process.exit(0);
|
|
7744
7887
|
});
|
|
7745
7888
|
}
|
|
7889
|
+
|
|
7890
|
+
// src/cli/retro.ts
|
|
7891
|
+
init_retroMeta();
|
|
7892
|
+
async function runRetroDone(now) {
|
|
7893
|
+
const { mutateStore: mutateStore2, readCurrentWorkflow: readCurrentWorkflow2, writeCurrentWorkflow: writeCurrentWorkflow2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
7894
|
+
const { repoKey: repoKey2 } = await Promise.resolve().then(() => (init_workflowStore(), workflowStore_exports));
|
|
7895
|
+
const key = repoKey2();
|
|
7896
|
+
await mutateStore2((store) => {
|
|
7897
|
+
const meta = { ...store.retro_meta ?? {} };
|
|
7898
|
+
meta[key] = resetRetro(now);
|
|
7899
|
+
return { ...store, retro_meta: meta };
|
|
7900
|
+
});
|
|
7901
|
+
const wf = await readCurrentWorkflow2();
|
|
7902
|
+
if (wf?.retro_due) await writeCurrentWorkflow2({ ...wf, retro_due: false });
|
|
7903
|
+
return { reset: true };
|
|
7904
|
+
}
|
|
7905
|
+
function registerRetro(program2) {
|
|
7906
|
+
program2.command("retro").description("Retro cadence: --done resets the phase counter after running /retro").option(
|
|
7907
|
+
"--done",
|
|
7908
|
+
"mark a retro as done \u2014 reset the phase counter + clear the RETRO-DUE reminder"
|
|
7909
|
+
).action(async (opts) => {
|
|
7910
|
+
if (!opts.done) {
|
|
7911
|
+
console.error("[harnessed] retro: nothing to do \u2014 pass --done after running /retro");
|
|
7912
|
+
process.exit(1);
|
|
7913
|
+
return;
|
|
7914
|
+
}
|
|
7915
|
+
await runRetroDone((/* @__PURE__ */ new Date()).toISOString());
|
|
7916
|
+
console.log("[harnessed] retro recorded \u2014 phase counter reset, RETRO-DUE reminder cleared");
|
|
7917
|
+
process.exit(0);
|
|
7918
|
+
});
|
|
7919
|
+
}
|
|
7746
7920
|
init_backup();
|
|
7747
7921
|
function normalizeEol(buf, eol) {
|
|
7748
7922
|
const lf = buf.toString("utf8").replace(/\r\n/g, "\n");
|
|
@@ -9238,6 +9412,7 @@ registerWorkflows(program);
|
|
|
9238
9412
|
registerLearn(program);
|
|
9239
9413
|
registerUpdate(program);
|
|
9240
9414
|
registerReleasePreflight(program);
|
|
9415
|
+
registerRetro(program);
|
|
9241
9416
|
program.parse(process.argv);
|
|
9242
9417
|
//# sourceMappingURL=cli.mjs.map
|
|
9243
9418
|
//# sourceMappingURL=cli.mjs.map
|