@tacuchi/agent-workflow-cli 21.8.0 → 21.9.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/dist/adapters/git-cli.js +217 -0
- package/dist/adapters/git-cli.js.map +1 -1
- package/dist/application/flow/flow-service.js +7 -0
- package/dist/application/flow/flow-service.js.map +1 -1
- package/dist/application/flow/internal-actions.js +5 -0
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/history-table.js +37 -0
- package/dist/application/history-table.js.map +1 -1
- package/dist/application/local-proposal.js +37 -0
- package/dist/application/local-proposal.js.map +1 -1
- package/dist/application/retirement/apply.js +448 -0
- package/dist/application/retirement/apply.js.map +1 -0
- package/dist/application/retirement/attribution.js +395 -0
- package/dist/application/retirement/attribution.js.map +1 -0
- package/dist/application/retirement/graph.js +196 -0
- package/dist/application/retirement/graph.js.map +1 -0
- package/dist/application/retirement/history-events.js +158 -0
- package/dist/application/retirement/history-events.js.map +1 -0
- package/dist/application/retirement/journal.js +113 -0
- package/dist/application/retirement/journal.js.map +1 -0
- package/dist/application/retirement/prepare.js +233 -0
- package/dist/application/retirement/prepare.js.map +1 -0
- package/dist/application/retirement/preview.js +101 -0
- package/dist/application/retirement/preview.js.map +1 -0
- package/dist/application/retirement/resolve.js +356 -0
- package/dist/application/retirement/resolve.js.map +1 -0
- package/dist/application/session-create-service.js +103 -12
- package/dist/application/session-create-service.js.map +1 -1
- package/dist/application/session-custody-recorder.js +137 -0
- package/dist/application/session-custody-recorder.js.map +1 -0
- package/dist/application/session-custody-service.js +171 -0
- package/dist/application/session-custody-service.js.map +1 -0
- package/dist/application/status-service.js +5 -0
- package/dist/application/status-service.js.map +1 -1
- package/dist/application/workline-index-service.js +31 -0
- package/dist/application/workline-index-service.js.map +1 -1
- package/dist/application/worktree-service.js +47 -1
- package/dist/application/worktree-service.js.map +1 -1
- package/dist/cli/commands/index.js +6 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/retirement.js +137 -0
- package/dist/cli/commands/retirement.js.map +1 -0
- package/dist/cli/commands/session-create.js +4 -1
- package/dist/cli/commands/session-create.js.map +1 -1
- package/dist/cli/help-groups.js +4 -0
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/domain/flow/authority.js +8 -0
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/retirement/proposal.js +83 -0
- package/dist/domain/retirement/proposal.js.map +1 -0
- package/dist/domain/retirement/selector.js +109 -0
- package/dist/domain/retirement/selector.js.map +1 -0
- package/dist/domain/session/custody.js +176 -0
- package/dist/domain/session/custody.js.map +1 -0
- package/dist/domain/workline-node.js +56 -0
- package/dist/domain/workline-node.js.map +1 -0
- package/package.json +1 -1
- package/skills/w/commands/discard.md +49 -0
- package/skills/w/commands/reset.md +50 -0
- package/skills/w/context/MANIFEST.json +147 -139
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exact retirement somebody is about to authorize — under ONE seal.
|
|
3
|
+
*
|
|
4
|
+
* `LocalProposal` seals a publication: the bytes that will be written, the bases
|
|
5
|
+
* they were computed from, the effect classes. A retirement is the mirror of it
|
|
6
|
+
* and needs the same guarantee for a different set of facts: what DISAPPEARS,
|
|
7
|
+
* what goes back to bytes it used to have, which uncommitted change is dropped,
|
|
8
|
+
* which commit gets a revert, which unit is reconciled and which row is appended.
|
|
9
|
+
* So it is a record of its own, sealed the same way and over all of it.
|
|
10
|
+
*
|
|
11
|
+
* Two properties are what the seal buys, and both are properties of the data
|
|
12
|
+
* rather than rules somebody has to remember:
|
|
13
|
+
*
|
|
14
|
+
* - **An identical retry never re-asks.** Same closure, same bytes, same SHAs →
|
|
15
|
+
* same digest → the approval still fits.
|
|
16
|
+
* - **Anything material that moved always re-asks.** One more session in the
|
|
17
|
+
* closure, a plan whose bytes changed since the preview, a commit that gained a
|
|
18
|
+
* descendant: all of them change the digest, and the old approval stops fitting.
|
|
19
|
+
*
|
|
20
|
+
* The `read_set` is the compare-and-swap. A retirement is computed FROM a state of
|
|
21
|
+
* the workspace — the board, each session's custody, git — and applying it later
|
|
22
|
+
* is only legitimate while that state still holds. Recording what was read, with
|
|
23
|
+
* its digest, is what lets `apply` refuse instead of acting on a world that moved.
|
|
24
|
+
*
|
|
25
|
+
* The preview is DERIVED from this object and never authored beside it, for the
|
|
26
|
+
* same reason a publication's is: two descriptions of the same deletion can
|
|
27
|
+
* disagree, and the one a person reads would be the one that is wrong.
|
|
28
|
+
*/
|
|
29
|
+
import { semanticDigest } from "../../application/semantic-operation/protocol.js";
|
|
30
|
+
export const RETIREMENT_MODES = ["discard", "reset"];
|
|
31
|
+
export const RETIREMENT_PROPOSAL_VERSION = 1;
|
|
32
|
+
export function sealRetirementProposal(input) {
|
|
33
|
+
const body = { version: RETIREMENT_PROPOSAL_VERSION, ...input };
|
|
34
|
+
return { ...body, digest: retirementDigest(body) };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The seal, over the SET rather than the order a builder happened to append in —
|
|
38
|
+
* except for `closure` and `reverts`, whose ORDER is material: a closure removed
|
|
39
|
+
* in another order can leave a dangling reference, and reverts applied in another
|
|
40
|
+
* order can conflict. Content travels as its own digest so the seal stays a fixed
|
|
41
|
+
* size whatever the restored bytes weigh.
|
|
42
|
+
*/
|
|
43
|
+
export function retirementDigest(body) {
|
|
44
|
+
return semanticDigest({
|
|
45
|
+
version: body.version,
|
|
46
|
+
mode: body.mode,
|
|
47
|
+
target: body.target,
|
|
48
|
+
closure: body.closure,
|
|
49
|
+
deletes: [...body.deletes].sort((a, b) => order(a.path, b.path)),
|
|
50
|
+
restores: [...body.restores]
|
|
51
|
+
.map((r) => ({
|
|
52
|
+
path: r.path,
|
|
53
|
+
existed: r.existed,
|
|
54
|
+
digest: r.digest,
|
|
55
|
+
current_digest: r.current_digest,
|
|
56
|
+
}))
|
|
57
|
+
.sort((a, b) => order(a.path, b.path)),
|
|
58
|
+
bindings: [...body.bindings].sort(order),
|
|
59
|
+
units: [...body.units].sort((a, b) => order(`${a.alias}/${a.session}`, `${b.alias}/${b.session}`)),
|
|
60
|
+
dirty: [...body.dirty]
|
|
61
|
+
.map((d) => ({ ...d, paths: [...d.paths].sort(order) }))
|
|
62
|
+
.sort((a, b) => order(a.alias, b.alias)),
|
|
63
|
+
reverts: body.reverts,
|
|
64
|
+
publication: body.publication,
|
|
65
|
+
event: body.event,
|
|
66
|
+
read_set: [...body.read_set].sort((a, b) => order(a.id, b.id)),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function order(a, b) {
|
|
70
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Whether this proposal would touch git history at all.
|
|
74
|
+
*
|
|
75
|
+
* It is what decides whether the authorization has to be LABELLED with its
|
|
76
|
+
* reverts: approving a retirement that only deletes local files is not the same
|
|
77
|
+
* decision as approving one that adds commits to a branch, and the difference has
|
|
78
|
+
* to be visible in the question rather than in the preview somebody may not read.
|
|
79
|
+
*/
|
|
80
|
+
export function proposesReverts(proposal) {
|
|
81
|
+
return proposal.reverts.length > 0;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=proposal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../../src/domain/retirement/proposal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming what to retire, unambiguously or not at all.
|
|
3
|
+
*
|
|
4
|
+
* A destructive command cannot take "024" and decide what the person meant. The
|
|
5
|
+
* board holds a spec 024, a plan 024 and a session 024 at the same time, and each
|
|
6
|
+
* one is a different amount of work to delete. So the selector is EXPLICIT by
|
|
7
|
+
* default (`plan:024`), a bare number is accepted only while exactly one node
|
|
8
|
+
* answers to it, and a path is accepted because a path is already unambiguous.
|
|
9
|
+
*
|
|
10
|
+
* Parsing here is pure: it says what SHAPE was asked for. Whether that shape
|
|
11
|
+
* resolves to one node, several, or none is the resolver's job — the two are kept
|
|
12
|
+
* apart so a parse error and an ambiguous target never come back as the same
|
|
13
|
+
* failure.
|
|
14
|
+
*/
|
|
15
|
+
import { isWorklineKind, nodeFromDocPath } from "../workline-node.js";
|
|
16
|
+
const BARE_NUMBER = /^\d{1,4}$/;
|
|
17
|
+
const SESSION_FOLDER = /^(?:session)?\d{3,}-[A-Za-z0-9._-]+$/;
|
|
18
|
+
export function parseTargetSelector(raw) {
|
|
19
|
+
const text = raw.trim();
|
|
20
|
+
if (text.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
problem: {
|
|
24
|
+
code: "TARGET_EMPTY",
|
|
25
|
+
message: "no se indicó ningún objetivo",
|
|
26
|
+
action: "pasá el objetivo: spec:<NNN> | plan:<PPP> | quick:<NNN> | session:<NNN|carpeta> | una ruta exacta",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// A path is checked first: `docs/plans/024-plan-x.md` contains no `:` on any
|
|
31
|
+
// platform we support, but a Windows spelling could, and the path reading is
|
|
32
|
+
// the more specific of the two.
|
|
33
|
+
const fromPath = nodeFromDocPath(text);
|
|
34
|
+
if (fromPath !== null) {
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
selector: {
|
|
38
|
+
form: "path",
|
|
39
|
+
path: normalizeSlashes(text),
|
|
40
|
+
kind: fromPath.kind,
|
|
41
|
+
key: fromPath.key,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const colon = text.indexOf(":");
|
|
46
|
+
if (colon > 0)
|
|
47
|
+
return parseQualified(text, colon);
|
|
48
|
+
if (BARE_NUMBER.test(text))
|
|
49
|
+
return { ok: true, selector: { form: "bare", key: pad(text) } };
|
|
50
|
+
if (SESSION_FOLDER.test(text))
|
|
51
|
+
return { ok: true, selector: { form: "folder", folder: text } };
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
problem: {
|
|
55
|
+
code: "TARGET_MALFORMED",
|
|
56
|
+
message: `'${raw}' no es un objetivo reconocible`,
|
|
57
|
+
action: "usá spec:<NNN>, plan:<PPP>, quick:<NNN>, session:<NNN|carpeta> o la ruta exacta del documento",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function parseQualified(text, colon) {
|
|
62
|
+
const kind = text.slice(0, colon).trim().toLowerCase();
|
|
63
|
+
const key = text.slice(colon + 1).trim();
|
|
64
|
+
if (!isWorklineKind(kind)) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
problem: {
|
|
68
|
+
code: "TARGET_UNKNOWN_KIND",
|
|
69
|
+
message: `'${kind}' no es una clase de nodo de Workline`,
|
|
70
|
+
action: "las clases son spec, plan, quick y session",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (key.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
problem: {
|
|
78
|
+
code: "TARGET_MALFORMED",
|
|
79
|
+
message: `'${kind}:' no nombra nada`,
|
|
80
|
+
action: `pasá ${kind}:<identidad>`,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// A session or a quick is addressed by number OR by folder; a spec and a plan
|
|
85
|
+
// only ever by number, and padding it is what makes `plan:24` and `plan:024`
|
|
86
|
+
// the same request instead of one that silently finds nothing.
|
|
87
|
+
const normalized = BARE_NUMBER.test(key) ? pad(key) : key;
|
|
88
|
+
return { ok: true, selector: { form: "qualified", kind, key: normalized } };
|
|
89
|
+
}
|
|
90
|
+
function pad(key) {
|
|
91
|
+
return key.padStart(3, "0");
|
|
92
|
+
}
|
|
93
|
+
function normalizeSlashes(path) {
|
|
94
|
+
return path.split("\\").join("/");
|
|
95
|
+
}
|
|
96
|
+
/** How the selector reads back to a person, verbatim in a rejection. */
|
|
97
|
+
export function selectorText(selector) {
|
|
98
|
+
switch (selector.form) {
|
|
99
|
+
case "qualified":
|
|
100
|
+
return `${selector.kind}:${selector.key}`;
|
|
101
|
+
case "bare":
|
|
102
|
+
return selector.key;
|
|
103
|
+
case "path":
|
|
104
|
+
return selector.path;
|
|
105
|
+
case "folder":
|
|
106
|
+
return selector.folder;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.js","sourceRoot":"","sources":["../../../src/domain/retirement/selector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a session RECEIVED, what it CREATED and what it CHANGED — sealed, and
|
|
3
|
+
* written before the session can produce a single effect.
|
|
4
|
+
*
|
|
5
|
+
* `SESSION.md`'s `## Origin` was the only record of where a run came from, and it
|
|
6
|
+
* is prose: it can say "derived from the plan 024" and still not answer the two
|
|
7
|
+
* questions a retirement has to answer before deleting anything. Which artifacts
|
|
8
|
+
* existed BEFORE this run, byte for byte? And which of the things that exist now
|
|
9
|
+
* are this run's, as opposed to somebody else's work that happens to sit in the
|
|
10
|
+
* same tree? Neither is derivable from names, dates or tags, and guessing either
|
|
11
|
+
* one wrong destroys work nobody can reconstruct.
|
|
12
|
+
*
|
|
13
|
+
* So custody is the authority and `## Origin` becomes its human projection:
|
|
14
|
+
*
|
|
15
|
+
* - **Born with the session, in the same operation.** A session that exists
|
|
16
|
+
* without custody could already have mutated something by the time anybody
|
|
17
|
+
* thinks to record a baseline, and a baseline taken afterwards is a fiction.
|
|
18
|
+
* - **A baseline is bytes or an explicit absence.** "The file was not there" is a
|
|
19
|
+
* fact a restore needs as much as its previous content; leaving it implicit is
|
|
20
|
+
* what turns a reset into a delete.
|
|
21
|
+
* - **Effects are receipts, never inferences.** A commit's SHAs come back from
|
|
22
|
+
* the commit that made them. Reading ownership off a commit MESSAGE would make
|
|
23
|
+
* the advisor's convention load-bearing, and a message is not evidence.
|
|
24
|
+
* - **Incomplete is a refusal, not a partial answer.** A custody that cannot say
|
|
25
|
+
* what it received blocks the mutation that would promise retirement later,
|
|
26
|
+
* because the alternative is promising a retirement that cannot be delivered.
|
|
27
|
+
*
|
|
28
|
+
* Custody lives INSIDE its session folder and dies with it. A durable record
|
|
29
|
+
* outside would outlive the thing it describes — a parallel history of deleted
|
|
30
|
+
* work, which is precisely the surface `S025/AC-04` says must not exist.
|
|
31
|
+
*/
|
|
32
|
+
import { semanticDigest } from "../../application/semantic-operation/protocol.js";
|
|
33
|
+
/** Bumped only when a reader can no longer trust the older shape. */
|
|
34
|
+
export const CUSTODY_VERSION = 1;
|
|
35
|
+
/** Session-local file the record lives in. Dot-prefixed: it is not an artifact. */
|
|
36
|
+
export const CUSTODY_FILE = ".custody.json";
|
|
37
|
+
/** The one way to digest a baseline's bytes, shared with the proposal's bases. */
|
|
38
|
+
export function baselineDigest(text) {
|
|
39
|
+
return semanticDigest(text);
|
|
40
|
+
}
|
|
41
|
+
/** A baseline for a path that was not there. Explicit, never an absent field. */
|
|
42
|
+
export const ABSENT_BASELINE = {
|
|
43
|
+
existed: false,
|
|
44
|
+
digest: null,
|
|
45
|
+
bytes: null,
|
|
46
|
+
content: null,
|
|
47
|
+
};
|
|
48
|
+
/** A baseline that preserves the previous text, for what only we can restore. */
|
|
49
|
+
export function preservedBaseline(content) {
|
|
50
|
+
return {
|
|
51
|
+
existed: true,
|
|
52
|
+
digest: baselineDigest(content),
|
|
53
|
+
bytes: Buffer.byteLength(content, "utf8"),
|
|
54
|
+
content,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function sealCustody(input) {
|
|
58
|
+
const body = {
|
|
59
|
+
version: CUSTODY_VERSION,
|
|
60
|
+
subject: input.subject,
|
|
61
|
+
subject_path: input.subjectPath,
|
|
62
|
+
parents: [...(input.parents ?? [])],
|
|
63
|
+
created: input.created,
|
|
64
|
+
artifacts: [...(input.artifacts ?? [])],
|
|
65
|
+
sources: [...(input.sources ?? [])],
|
|
66
|
+
effects: [...(input.effects ?? [])],
|
|
67
|
+
};
|
|
68
|
+
return { ...body, digest: custodyDigest(body) };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The seal, over the SET rather than the order things were appended in.
|
|
72
|
+
*
|
|
73
|
+
* Two custodies that recorded the same facts in a different order are the same
|
|
74
|
+
* custody, and a digest that disagreed would report tampering on a detail nobody
|
|
75
|
+
* can see. Content travels as its own digest so the seal stays a fixed size
|
|
76
|
+
* whatever the preserved bytes weigh.
|
|
77
|
+
*/
|
|
78
|
+
export function custodyDigest(body) {
|
|
79
|
+
return semanticDigest({
|
|
80
|
+
version: body.version,
|
|
81
|
+
subject: body.subject,
|
|
82
|
+
subject_path: body.subject_path,
|
|
83
|
+
parents: [...body.parents].sort(nodeOrder),
|
|
84
|
+
created: body.created,
|
|
85
|
+
artifacts: [...body.artifacts]
|
|
86
|
+
.map((a) => ({
|
|
87
|
+
path: a.path,
|
|
88
|
+
role: a.role,
|
|
89
|
+
before: {
|
|
90
|
+
existed: a.before.existed,
|
|
91
|
+
digest: a.before.digest,
|
|
92
|
+
bytes: a.before.bytes,
|
|
93
|
+
content_digest: a.before.content === null ? null : baselineDigest(a.before.content),
|
|
94
|
+
},
|
|
95
|
+
}))
|
|
96
|
+
.sort((a, b) => order(a.path, b.path)),
|
|
97
|
+
sources: [...body.sources].sort((a, b) => order(a.alias, b.alias)),
|
|
98
|
+
effects: [...body.effects],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function nodeOrder(a, b) {
|
|
102
|
+
return order(`${a.kind}:${a.key}`, `${b.kind}:${b.key}`);
|
|
103
|
+
}
|
|
104
|
+
/** Code-unit ordering, the same reason `canonicalJson` avoids `localeCompare`. */
|
|
105
|
+
function order(a, b) {
|
|
106
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether this record can still answer "what did the session receive".
|
|
110
|
+
*
|
|
111
|
+
* Called at two very different moments and that is deliberate: before a mutation
|
|
112
|
+
* that promises retirement, and again before a retirement is prepared. The first
|
|
113
|
+
* refuses to make the promise; the second refuses to pretend it was kept. Both
|
|
114
|
+
* read the same rule, so a session that was allowed to mutate is never one whose
|
|
115
|
+
* retirement turns out to be unprovable.
|
|
116
|
+
*/
|
|
117
|
+
export function custodyCompleteness(custody) {
|
|
118
|
+
const gaps = [];
|
|
119
|
+
if (custody.version !== CUSTODY_VERSION) {
|
|
120
|
+
gaps.push({
|
|
121
|
+
what: `versión de custodia ${custody.version}`,
|
|
122
|
+
why: `este CLI sella y lee la versión ${CUSTODY_VERSION}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (custody.digest !== custodyDigest(stripDigest(custody))) {
|
|
126
|
+
gaps.push({
|
|
127
|
+
what: "sello de la custodia",
|
|
128
|
+
why: "el registro no coincide con su digest: fue editado fuera del CLI",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
for (const artifact of custody.artifacts) {
|
|
132
|
+
if (artifact.role !== "input")
|
|
133
|
+
continue;
|
|
134
|
+
if (artifact.before.existed && artifact.before.digest === null) {
|
|
135
|
+
gaps.push({
|
|
136
|
+
what: `baseline de '${artifact.path}'`,
|
|
137
|
+
why: "la entrada existía y no se conservó su estado previo",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const source of custody.sources) {
|
|
142
|
+
if (source.baseline_head === null && source.unit_branch !== null) {
|
|
143
|
+
gaps.push({
|
|
144
|
+
what: `baseline git de '${source.alias}'`,
|
|
145
|
+
why: "la sesión tomó una unidad sobre una fuente cuyo HEAD no se registró",
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { complete: gaps.length === 0, gaps };
|
|
150
|
+
}
|
|
151
|
+
function stripDigest(custody) {
|
|
152
|
+
const { digest: _digest, ...body } = custody;
|
|
153
|
+
return body;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Every commit this session can prove is its own, per source.
|
|
157
|
+
*
|
|
158
|
+
* Two independent readings, and both are receipts rather than heuristics: the
|
|
159
|
+
* commits a typed commit reported making, and — for a session with its own unit
|
|
160
|
+
* — the range its branch advanced over its recorded baseline. The second is what
|
|
161
|
+
* covers commits the agent made with plain git inside its unit: the branch is
|
|
162
|
+
* the session's by convention and the baseline is sealed, so the range is
|
|
163
|
+
* exclusive by construction and needs nobody's commit message.
|
|
164
|
+
*/
|
|
165
|
+
export function attributableCommits(custody, alias) {
|
|
166
|
+
const seen = new Set();
|
|
167
|
+
for (const effect of custody.effects) {
|
|
168
|
+
if (effect.kind !== "commit" && effect.kind !== "unit_integrated")
|
|
169
|
+
continue;
|
|
170
|
+
if (effect.alias !== alias || effect.after === null)
|
|
171
|
+
continue;
|
|
172
|
+
seen.add(effect.after);
|
|
173
|
+
}
|
|
174
|
+
return [...seen];
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=custody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custody.js","sourceRoot":"","sources":["../../../src/domain/session/custody.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity of a Workline node, said the same way by everybody who points at
|
|
3
|
+
* one.
|
|
4
|
+
*
|
|
5
|
+
* Provenance is a graph over four kinds of thing — a spec, a plan, a quick and a
|
|
6
|
+
* session — and until now each reader spelled them its own way: the index keys
|
|
7
|
+
* plans by `number`, sessions by folder, and a session's `## Origin` names its
|
|
8
|
+
* parent in prose. Prose is exactly what cannot carry an edge: two readings of
|
|
9
|
+
* "derived from the plan 024" can disagree about which document that is, and a
|
|
10
|
+
* retirement that guessed wrong would delete somebody else's work.
|
|
11
|
+
*
|
|
12
|
+
* So a node has ONE canonical id, `<kind>:<key>`, and every edge is a pair of
|
|
13
|
+
* those. The path is carried beside the id rather than inside it because a
|
|
14
|
+
* document can move: identity is `plan:024`, the path is where it lives today,
|
|
15
|
+
* and a stale path is a warning while a wrong id is a defect.
|
|
16
|
+
*/
|
|
17
|
+
export const WORKLINE_KINDS = ["spec", "plan", "quick", "session"];
|
|
18
|
+
const KIND_SET = new Set(WORKLINE_KINDS);
|
|
19
|
+
export function isWorklineKind(value) {
|
|
20
|
+
return typeof value === "string" && KIND_SET.has(value);
|
|
21
|
+
}
|
|
22
|
+
/** `plan:024` — the one spelling of a node, in output and in a seal alike. */
|
|
23
|
+
export function formatNodeId(id) {
|
|
24
|
+
return `${id.kind}:${id.key}`;
|
|
25
|
+
}
|
|
26
|
+
const DOC_PATH_RE = /(?:^|\/)docs\/(specs|plans)\/(\d{3})-(spec|plan)(?:-[^/]*)?\.md$/i;
|
|
27
|
+
/**
|
|
28
|
+
* The node a document path IS — `docs/plans/024-plan-x.md` is `plan:024`.
|
|
29
|
+
*
|
|
30
|
+
* This is not inferring provenance from a name: the workspace's own layout fixes
|
|
31
|
+
* that a spec lives at `docs/specs/NNN-spec-*.md`, and the board already reads
|
|
32
|
+
* every spec and plan by exactly this shape. Identity is what the path declares;
|
|
33
|
+
* what the document is DERIVED from is a different question, answered by the
|
|
34
|
+
* document's own `Derived from` line or by a session's sealed custody.
|
|
35
|
+
*/
|
|
36
|
+
export function nodeFromDocPath(path) {
|
|
37
|
+
const m = DOC_PATH_RE.exec(path.split("\\").join("/"));
|
|
38
|
+
if (m?.[2] === undefined)
|
|
39
|
+
return null;
|
|
40
|
+
return { kind: m[1]?.toLowerCase() === "specs" ? "spec" : "plan", key: m[2] };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Why an edge is believed.
|
|
44
|
+
*
|
|
45
|
+
* `custody` and `derived-from` are provable: one was sealed by the CLI when the
|
|
46
|
+
* node was born, the other is the plan's own machine-readable `Derived from`
|
|
47
|
+
* line. `origin-prose` is the legacy reading of a free-text `## Origin`, and it
|
|
48
|
+
* is kept SEPARATE precisely so a retirement can refuse to act on it — an edge
|
|
49
|
+
* nobody can prove is the one case where deleting is not recoverable.
|
|
50
|
+
*/
|
|
51
|
+
export const EDGE_EVIDENCE = ["custody", "derived-from", "origin-prose"];
|
|
52
|
+
/** Whether an edge is strong enough for a destructive closure to include it. */
|
|
53
|
+
export function isProvable(evidence) {
|
|
54
|
+
return evidence === "custody" || evidence === "derived-from";
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=workline-node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workline-node.js","sourceRoot":"","sources":["../../src/domain/workline-node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAU,CAAC;AAa5E,MAAM,QAAQ,GAAwB,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC;AAE9D,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,EAAkB;IAC7C,OAAO,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,WAAW,GAAG,mEAAmE,CAAC;AAExF;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACvD,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAChF,CAAC;AAiBD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,cAAc,EAAE,cAAc,CAAU,CAAC;AAIlF,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,QAAsB;IAC/C,OAAO,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,cAAc,CAAC;AAC/D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tacuchi/agent-workflow-cli",
|
|
3
|
-
"version": "21.
|
|
3
|
+
"version": "21.9.0",
|
|
4
4
|
"description": "Runtime CLI for Workline — the stages + loops + artifacts system for agent work. Bundles the universal `w` skill set under `skills/w/` (slash commands `/w:*`: spec-new/spec-refine, plan-new/plan-exec, quick, persist, workspace-init, export-*); `self install --target <host>` copies SKILL + commands + hooks into the host. Pluggable capability skills via `.workflow/skills.toml`. Multi-empresa parametrization via `profile.json` cascade. Namespace auto-detected from any `.<ns>/sessions/` dir in CWD; default `workflow`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use to retire a spec, plan, quick or session for good with everything it exclusively owns. `aw discard` resolves the closure from sealed provenance, previews it whole and applies it all-or-nothing on one approval. Never rewrites git history.
|
|
3
|
+
argument-hint: "<spec:NNN | plan:PPP | quick:NNN | session:NNN|carpeta | ruta>"
|
|
4
|
+
allowed-tools: ["Bash", "Read"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# discard — retire work and everything it exclusively owns
|
|
8
|
+
|
|
9
|
+
No loop, no session — the closure can include the session that would be driving it.
|
|
10
|
+
Output in the **user's language**.
|
|
11
|
+
|
|
12
|
+
**Hard floor:**
|
|
13
|
+
|
|
14
|
+
1. **`prepare` first, always** — read-only: no session, no journal, no file, no ref,
|
|
15
|
+
so a wrong target costs nothing.
|
|
16
|
+
2. **You never decide the scope.** It comes from sealed custody and each document's
|
|
17
|
+
`Derived from`; a descendant nobody can prove is a REFUSAL, not a guess.
|
|
18
|
+
3. **One approval, over the exact digest.** `apply` recomputes under the workspace
|
|
19
|
+
lock and refuses if anything material moved. Approving is not applying.
|
|
20
|
+
4. **Reverts are commits, never rewrites** — no `reset --hard`, rebase, amend, force
|
|
21
|
+
or push; a published commit's revert leaves its push pending and external.
|
|
22
|
+
|
|
23
|
+
## Run
|
|
24
|
+
|
|
25
|
+
1. `aw discard prepare <objetivo> --format human` — what disappears, which local
|
|
26
|
+
change is dropped, which SHA gets a revert, which unit is reconciled, which
|
|
27
|
+
`HISTORY` row it adds, and the **digest**.
|
|
28
|
+
2. Show that preview and ask approval. If it touches git history, say so: approving
|
|
29
|
+
also authorizes commits.
|
|
30
|
+
3. `aw discard apply <objetivo> --approval <digest> --format human`.
|
|
31
|
+
4. A rejection names cause, candidates and next action; **nothing was applied**.
|
|
32
|
+
`024` resolves only while one node answers to it.
|
|
33
|
+
|
|
34
|
+
## What the CLI decides (do not re-derive)
|
|
35
|
+
|
|
36
|
+
- **The closure**, in removal order; a node something outside also descends from
|
|
37
|
+
blocks the whole operation.
|
|
38
|
+
- **Attribution** of changes and commits, from baselines and receipts — never from a
|
|
39
|
+
message, an author or a tag.
|
|
40
|
+
- **Whether all-or-nothing holds**: two publication units, a conflicting revert, an
|
|
41
|
+
operation in progress or an unsyncable tree block before anything mutates.
|
|
42
|
+
- **What survives**: the originals stay reachable and the only durable Workline trace
|
|
43
|
+
is one append-only `HISTORY` row.
|
|
44
|
+
|
|
45
|
+
## More context
|
|
46
|
+
|
|
47
|
+
`aw context-plan --command discard --root "${CLAUDE_PLUGIN_ROOT}/skills/w"` lists what to read.
|
|
48
|
+
|
|
49
|
+
- Back to before an **incomplete** session instead: `/w:reset`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use to undo an incomplete session and put its document back as it was before that session ran. `aw reset` restores the inputs byte for byte and retires the session, all-or-nothing on one approval. Never rewrites git history.
|
|
3
|
+
argument-hint: "<plan:PPP | session:NNN|carpeta | ruta>"
|
|
4
|
+
allowed-tools: ["Bash", "Read"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# reset — put an incomplete session's inputs back
|
|
8
|
+
|
|
9
|
+
No loop, no session. Output in the **user's language**. It is **not**
|
|
10
|
+
`/w:plan-refine`: refining carries a document forward keeping validated phases, this
|
|
11
|
+
takes it back to the bytes it had before the session started.
|
|
12
|
+
|
|
13
|
+
**Hard floor:**
|
|
14
|
+
|
|
15
|
+
1. **`prepare` first, always** — read-only, so a wrong target costs nothing.
|
|
16
|
+
2. **Only an INCOMPLETE session.** One that converged does not become resettable by
|
|
17
|
+
being selected, and `.closed` is never the test: a session closed with work
|
|
18
|
+
pending is exactly this case.
|
|
19
|
+
3. **The target must resolve to ONE incomplete session**; two is a rejection with its
|
|
20
|
+
candidates, and then you name the session.
|
|
21
|
+
4. **One approval, over the exact digest.** `apply` recomputes under the lock and
|
|
22
|
+
refuses if the document moved since the preview.
|
|
23
|
+
5. **Reverts are commits, never rewrites** — no `reset --hard`, rebase, amend, force
|
|
24
|
+
or push, whatever this command's name suggests.
|
|
25
|
+
|
|
26
|
+
## Run
|
|
27
|
+
|
|
28
|
+
1. `aw reset prepare <objetivo> --format human` — which session it resolved, which
|
|
29
|
+
paths go back to their previous bytes and whether they moved since the baseline,
|
|
30
|
+
what disappears, which SHA gets a revert, and the **digest**.
|
|
31
|
+
2. Show that preview and ask approval; if it touches git history, say so.
|
|
32
|
+
3. `aw reset apply <objetivo> --approval <digest> --format human`.
|
|
33
|
+
4. A rejection names cause, candidates and next action; **nothing was applied**.
|
|
34
|
+
|
|
35
|
+
## What the CLI decides (do not re-derive)
|
|
36
|
+
|
|
37
|
+
- **Which session** an artifact resolves to, and whether it is incomplete.
|
|
38
|
+
- **What "before" was**: the baseline sealed at creation. Never reconstructed from
|
|
39
|
+
names, dates or tags — without it, it fails closed.
|
|
40
|
+
- **Inputs vs outputs**: an input returns to its bytes, an output is removed, and a
|
|
41
|
+
document with work of its own blocks instead of being orphaned. A quick with no
|
|
42
|
+
base document is retired whole.
|
|
43
|
+
- **All-or-nothing**: either the scope is intact, or the result is complete, not
|
|
44
|
+
resumable, and leaves one append-only `HISTORY` row.
|
|
45
|
+
|
|
46
|
+
## More context
|
|
47
|
+
|
|
48
|
+
`aw context-plan --command reset --root "${CLAUDE_PLUGIN_ROOT}/skills/w"` lists what to read.
|
|
49
|
+
|
|
50
|
+
- Deleting a document and everything under it: `/w:discard`.
|