wingfoil 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +334 -0
- package/dist/cli/error.d.ts +11 -0
- package/dist/cli/error.d.ts.map +1 -0
- package/dist/cli/error.js +27 -0
- package/dist/cli/error.js.map +1 -0
- package/dist/cli/exit.d.ts +19 -0
- package/dist/cli/exit.d.ts.map +1 -0
- package/dist/cli/exit.js +14 -0
- package/dist/cli/exit.js.map +1 -0
- package/dist/cli/index.d.ts +28 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +36 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-command.d.ts +34 -0
- package/dist/cli/init-command.d.ts.map +1 -0
- package/dist/cli/init-command.js +100 -0
- package/dist/cli/init-command.js.map +1 -0
- package/dist/cli/mcp-command.d.ts +24 -0
- package/dist/cli/mcp-command.d.ts.map +1 -0
- package/dist/cli/mcp-command.js +42 -0
- package/dist/cli/mcp-command.js.map +1 -0
- package/dist/cli/output.d.ts +13 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +28 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/program.d.ts +12 -0
- package/dist/cli/program.d.ts.map +1 -0
- package/dist/cli/program.js +279 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/registrar.d.ts +69 -0
- package/dist/cli/registrar.d.ts.map +1 -0
- package/dist/cli/registrar.js +82 -0
- package/dist/cli/registrar.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +45 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/approval-authority.d.ts +83 -0
- package/dist/core/approval-authority.d.ts.map +1 -0
- package/dist/core/approval-authority.js +112 -0
- package/dist/core/approval-authority.js.map +1 -0
- package/dist/core/builtin-asset.d.ts +71 -0
- package/dist/core/builtin-asset.d.ts.map +1 -0
- package/dist/core/builtin-asset.js +137 -0
- package/dist/core/builtin-asset.js.map +1 -0
- package/dist/core/builtin-integrity.d.ts +32 -0
- package/dist/core/builtin-integrity.d.ts.map +1 -0
- package/dist/core/builtin-integrity.js +148 -0
- package/dist/core/builtin-integrity.js.map +1 -0
- package/dist/core/confinement.d.ts +57 -0
- package/dist/core/confinement.d.ts.map +1 -0
- package/dist/core/confinement.js +108 -0
- package/dist/core/confinement.js.map +1 -0
- package/dist/core/context.d.ts +187 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +192 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/directive-assign.d.ts +131 -0
- package/dist/core/directive-assign.d.ts.map +1 -0
- package/dist/core/directive-assign.js +352 -0
- package/dist/core/directive-assign.js.map +1 -0
- package/dist/core/directives-list.d.ts +80 -0
- package/dist/core/directives-list.d.ts.map +1 -0
- package/dist/core/directives-list.js +148 -0
- package/dist/core/directives-list.js.map +1 -0
- package/dist/core/exit-code.d.ts +85 -0
- package/dist/core/exit-code.d.ts.map +1 -0
- package/dist/core/exit-code.js +163 -0
- package/dist/core/exit-code.js.map +1 -0
- package/dist/core/git-identity.d.ts +32 -0
- package/dist/core/git-identity.d.ts.map +1 -0
- package/dist/core/git-identity.js +72 -0
- package/dist/core/git-identity.js.map +1 -0
- package/dist/core/index.d.ts +388 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +1331 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/init.d.ts +78 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +167 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/loaders.d.ts +174 -0
- package/dist/core/loaders.d.ts.map +1 -0
- package/dist/core/loaders.js +364 -0
- package/dist/core/loaders.js.map +1 -0
- package/dist/core/memory-add-type.d.ts +43 -0
- package/dist/core/memory-add-type.d.ts.map +1 -0
- package/dist/core/memory-add-type.js +151 -0
- package/dist/core/memory-add-type.js.map +1 -0
- package/dist/core/memory-transition.d.ts +111 -0
- package/dist/core/memory-transition.d.ts.map +1 -0
- package/dist/core/memory-transition.js +269 -0
- package/dist/core/memory-transition.js.map +1 -0
- package/dist/core/registry.d.ts +189 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +66 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/relevance.d.ts +86 -0
- package/dist/core/relevance.d.ts.map +1 -0
- package/dist/core/relevance.js +258 -0
- package/dist/core/relevance.js.map +1 -0
- package/dist/core/require-reason.d.ts +27 -0
- package/dist/core/require-reason.d.ts.map +1 -0
- package/dist/core/require-reason.js +95 -0
- package/dist/core/require-reason.js.map +1 -0
- package/dist/core/types.d.ts +45 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +22 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/usage-error.d.ts +20 -0
- package/dist/core/usage-error.d.ts.map +1 -0
- package/dist/core/usage-error.js +29 -0
- package/dist/core/usage-error.js.map +1 -0
- package/dist/core/write-guard.d.ts +99 -0
- package/dist/core/write-guard.d.ts.map +1 -0
- package/dist/core/write-guard.js +247 -0
- package/dist/core/write-guard.js.map +1 -0
- package/dist/directives/create.d.ts +47 -0
- package/dist/directives/create.d.ts.map +1 -0
- package/dist/directives/create.js +82 -0
- package/dist/directives/create.js.map +1 -0
- package/dist/directives/index.d.ts +20 -0
- package/dist/directives/index.d.ts.map +1 -0
- package/dist/directives/index.js +32 -0
- package/dist/directives/index.js.map +1 -0
- package/dist/directives/roles-edit.d.ts +53 -0
- package/dist/directives/roles-edit.d.ts.map +1 -0
- package/dist/directives/roles-edit.js +286 -0
- package/dist/directives/roles-edit.js.map +1 -0
- package/dist/directives/schema.d.ts +65 -0
- package/dist/directives/schema.d.ts.map +1 -0
- package/dist/directives/schema.js +68 -0
- package/dist/directives/schema.js.map +1 -0
- package/dist/dna/edit.d.ts +61 -0
- package/dist/dna/edit.d.ts.map +1 -0
- package/dist/dna/edit.js +429 -0
- package/dist/dna/edit.js.map +1 -0
- package/dist/dna/index.d.ts +24 -0
- package/dist/dna/index.d.ts.map +1 -0
- package/dist/dna/index.js +59 -0
- package/dist/dna/index.js.map +1 -0
- package/dist/dna/mutate.d.ts +74 -0
- package/dist/dna/mutate.d.ts.map +1 -0
- package/dist/dna/mutate.js +318 -0
- package/dist/dna/mutate.js.map +1 -0
- package/dist/dna/path.d.ts +175 -0
- package/dist/dna/path.d.ts.map +1 -0
- package/dist/dna/path.js +289 -0
- package/dist/dna/path.js.map +1 -0
- package/dist/dna/roles.d.ts +57 -0
- package/dist/dna/roles.d.ts.map +1 -0
- package/dist/dna/roles.js +50 -0
- package/dist/dna/roles.js.map +1 -0
- package/dist/dna/schema.d.ts +172 -0
- package/dist/dna/schema.d.ts.map +1 -0
- package/dist/dna/schema.js +182 -0
- package/dist/dna/schema.js.map +1 -0
- package/dist/dna/set.d.ts +145 -0
- package/dist/dna/set.d.ts.map +1 -0
- package/dist/dna/set.js +428 -0
- package/dist/dna/set.js.map +1 -0
- package/dist/mcp/dna-resource.d.ts +16 -0
- package/dist/mcp/dna-resource.d.ts.map +1 -0
- package/dist/mcp/dna-resource.js +56 -0
- package/dist/mcp/dna-resource.js.map +1 -0
- package/dist/mcp/index.d.ts +49 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +60 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/memory-resource.d.ts +17 -0
- package/dist/mcp/memory-resource.d.ts.map +1 -0
- package/dist/mcp/memory-resource.js +133 -0
- package/dist/mcp/memory-resource.js.map +1 -0
- package/dist/mcp/prompt.d.ts +48 -0
- package/dist/mcp/prompt.d.ts.map +1 -0
- package/dist/mcp/prompt.js +176 -0
- package/dist/mcp/prompt.js.map +1 -0
- package/dist/mcp/read-only.d.ts +42 -0
- package/dist/mcp/read-only.d.ts.map +1 -0
- package/dist/mcp/read-only.js +93 -0
- package/dist/mcp/read-only.js.map +1 -0
- package/dist/mcp/registrar.d.ts +54 -0
- package/dist/mcp/registrar.d.ts.map +1 -0
- package/dist/mcp/registrar.js +72 -0
- package/dist/mcp/registrar.js.map +1 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +64 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/workflow-resource.d.ts +17 -0
- package/dist/mcp/workflow-resource.d.ts.map +1 -0
- package/dist/mcp/workflow-resource.js +63 -0
- package/dist/mcp/workflow-resource.js.map +1 -0
- package/dist/memory/add.d.ts +50 -0
- package/dist/memory/add.d.ts.map +1 -0
- package/dist/memory/add.js +131 -0
- package/dist/memory/add.js.map +1 -0
- package/dist/memory/audit.d.ts +177 -0
- package/dist/memory/audit.d.ts.map +1 -0
- package/dist/memory/audit.js +287 -0
- package/dist/memory/audit.js.map +1 -0
- package/dist/memory/commit-message.d.ts +146 -0
- package/dist/memory/commit-message.d.ts.map +1 -0
- package/dist/memory/commit-message.js +203 -0
- package/dist/memory/commit-message.js.map +1 -0
- package/dist/memory/entry.d.ts +56 -0
- package/dist/memory/entry.d.ts.map +1 -0
- package/dist/memory/entry.js +28 -0
- package/dist/memory/entry.js.map +1 -0
- package/dist/memory/frontmatter-edit.d.ts +68 -0
- package/dist/memory/frontmatter-edit.d.ts.map +1 -0
- package/dist/memory/frontmatter-edit.js +333 -0
- package/dist/memory/frontmatter-edit.js.map +1 -0
- package/dist/memory/git-log.d.ts +32 -0
- package/dist/memory/git-log.d.ts.map +1 -0
- package/dist/memory/git-log.js +100 -0
- package/dist/memory/git-log.js.map +1 -0
- package/dist/memory/history.d.ts +142 -0
- package/dist/memory/history.d.ts.map +1 -0
- package/dist/memory/history.js +250 -0
- package/dist/memory/history.js.map +1 -0
- package/dist/memory/index.d.ts +58 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +107 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/query.d.ts +150 -0
- package/dist/memory/query.d.ts.map +1 -0
- package/dist/memory/query.js +307 -0
- package/dist/memory/query.js.map +1 -0
- package/dist/memory/reject.d.ts +11 -0
- package/dist/memory/reject.d.ts.map +1 -0
- package/dist/memory/reject.js +32 -0
- package/dist/memory/reject.js.map +1 -0
- package/dist/memory/schema.d.ts +104 -0
- package/dist/memory/schema.d.ts.map +1 -0
- package/dist/memory/schema.js +161 -0
- package/dist/memory/schema.js.map +1 -0
- package/dist/memory/state-machine.d.ts +166 -0
- package/dist/memory/state-machine.d.ts.map +1 -0
- package/dist/memory/state-machine.js +431 -0
- package/dist/memory/state-machine.js.map +1 -0
- package/dist/memory/submit.d.ts +11 -0
- package/dist/memory/submit.d.ts.map +1 -0
- package/dist/memory/submit.js +44 -0
- package/dist/memory/submit.js.map +1 -0
- package/dist/storage/builtin-directives.d.ts +54 -0
- package/dist/storage/builtin-directives.d.ts.map +1 -0
- package/dist/storage/builtin-directives.js +152 -0
- package/dist/storage/builtin-directives.js.map +1 -0
- package/dist/storage/commit.d.ts +92 -0
- package/dist/storage/commit.d.ts.map +1 -0
- package/dist/storage/commit.js +201 -0
- package/dist/storage/commit.js.map +1 -0
- package/dist/storage/confinement.d.ts +93 -0
- package/dist/storage/confinement.d.ts.map +1 -0
- package/dist/storage/confinement.js +164 -0
- package/dist/storage/confinement.js.map +1 -0
- package/dist/storage/document.d.ts +27 -0
- package/dist/storage/document.d.ts.map +1 -0
- package/dist/storage/document.js +52 -0
- package/dist/storage/document.js.map +1 -0
- package/dist/storage/errors.d.ts +36 -0
- package/dist/storage/errors.d.ts.map +1 -0
- package/dist/storage/errors.js +46 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/frontmatter.d.ts +27 -0
- package/dist/storage/frontmatter.d.ts.map +1 -0
- package/dist/storage/frontmatter.js +37 -0
- package/dist/storage/frontmatter.js.map +1 -0
- package/dist/storage/git-root.d.ts +17 -0
- package/dist/storage/git-root.d.ts.map +1 -0
- package/dist/storage/git-root.js +49 -0
- package/dist/storage/git-root.js.map +1 -0
- package/dist/storage/index.d.ts +29 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +66 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/init-state.d.ts +9 -0
- package/dist/storage/init-state.d.ts.map +1 -0
- package/dist/storage/init-state.js +24 -0
- package/dist/storage/init-state.js.map +1 -0
- package/dist/storage/layout.d.ts +51 -0
- package/dist/storage/layout.d.ts.map +1 -0
- package/dist/storage/layout.js +88 -0
- package/dist/storage/layout.js.map +1 -0
- package/dist/storage/memory-path.d.ts +72 -0
- package/dist/storage/memory-path.d.ts.map +1 -0
- package/dist/storage/memory-path.js +127 -0
- package/dist/storage/memory-path.js.map +1 -0
- package/dist/storage/snapshot.d.ts +16 -0
- package/dist/storage/snapshot.d.ts.map +1 -0
- package/dist/storage/snapshot.js +72 -0
- package/dist/storage/snapshot.js.map +1 -0
- package/dist/storage/templates.d.ts +88 -0
- package/dist/storage/templates.d.ts.map +1 -0
- package/dist/storage/templates.js +422 -0
- package/dist/storage/templates.js.map +1 -0
- package/dist/validation/error-mapper.d.ts +16 -0
- package/dist/validation/error-mapper.d.ts.map +1 -0
- package/dist/validation/error-mapper.js +18 -0
- package/dist/validation/error-mapper.js.map +1 -0
- package/dist/validation/errors.d.ts +69 -0
- package/dist/validation/errors.d.ts.map +1 -0
- package/dist/validation/errors.js +69 -0
- package/dist/validation/errors.js.map +1 -0
- package/dist/validation/id.d.ts +16 -0
- package/dist/validation/id.d.ts.map +1 -0
- package/dist/validation/id.js +177 -0
- package/dist/validation/id.js.map +1 -0
- package/dist/validation/index.d.ts +18 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +37 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/secret-scan.d.ts +129 -0
- package/dist/validation/secret-scan.d.ts.map +1 -0
- package/dist/validation/secret-scan.js +367 -0
- package/dist/validation/secret-scan.js.map +1 -0
- package/dist/validation/two-pass.d.ts +24 -0
- package/dist/validation/two-pass.d.ts.map +1 -0
- package/dist/validation/two-pass.js +24 -0
- package/dist/validation/two-pass.js.map +1 -0
- package/dist/validation/warning.d.ts +31 -0
- package/dist/validation/warning.d.ts.map +1 -0
- package/dist/validation/warning.js +81 -0
- package/dist/validation/warning.js.map +1 -0
- package/dist/validation/yaml.d.ts +9 -0
- package/dist/validation/yaml.d.ts.map +1 -0
- package/dist/validation/yaml.js +30 -0
- package/dist/validation/yaml.js.map +1 -0
- package/dist/workflow/index.d.ts +10 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +16 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/schema.d.ts +89 -0
- package/dist/workflow/schema.d.ts.map +1 -0
- package/dist/workflow/schema.js +73 -0
- package/dist/workflow/schema.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setFrontmatterField = setFrontmatterField;
|
|
4
|
+
exports.removeFrontmatterField = removeFrontmatterField;
|
|
5
|
+
exports.verifyFrontmatterEdit = verifyFrontmatterEdit;
|
|
6
|
+
exports.verifyDocumentEdit = verifyDocumentEdit;
|
|
7
|
+
exports.describeDocumentChanges = describeDocumentChanges;
|
|
8
|
+
/**
|
|
9
|
+
* Line-based edits of top-level frontmatter fields (task-045-memory-submit).
|
|
10
|
+
*
|
|
11
|
+
* Memory transition verbs change a handful of fields — `status` on every verb, `rejection_reason` on
|
|
12
|
+
* `submit` (removed) and `reject` (set) — per `spec-010-memory-frontmatter-schema`'s field-write
|
|
13
|
+
* ownership table. Re-serializing the YAML would strip the templates' inline `# REQUIRED …` comments
|
|
14
|
+
* and normalise quoting everywhere (the defect class of `bug-004` for `dna set`), so these edits touch
|
|
15
|
+
* only the lines of the one entry being changed and leave every other byte — the rest of the
|
|
16
|
+
* frontmatter and the whole body — as it was.
|
|
17
|
+
*
|
|
18
|
+
* **What "the lines of an entry" are** is decided by YAML's own rules, not by indentation alone (the
|
|
19
|
+
* first pass's indentation-only rule removed half of a block scalar and folded the rest into the
|
|
20
|
+
* preceding key — rejection `d7b9d70`). An entry is its column-0 `key:` line plus:
|
|
21
|
+
*
|
|
22
|
+
* - a **block scalar** (`|`, `>`, with optional chomping/indentation indicators): every following line
|
|
23
|
+
* that is blank or indented at least as far as the scalar's content, `#`-lines included (they are
|
|
24
|
+
* content there);
|
|
25
|
+
* - a **quoted scalar** not closed on the key line: every line up to the closing quote;
|
|
26
|
+
* - a **plain scalar**: following indented lines that are not comments, with the blank lines between
|
|
27
|
+
* them (a plain scalar ends at a comment line);
|
|
28
|
+
* - an **empty value** (a nested mapping or sequence): following indented or `-` lines, with blank and
|
|
29
|
+
* comment lines — at ANY indentation, a column-0 `#` between two column-0 sequence items included —
|
|
30
|
+
* only when more of that nested block follows.
|
|
31
|
+
*
|
|
32
|
+
* Trailing blank lines, and a comment that ends a value, belong to the parent mapping and are kept.
|
|
33
|
+
* CRLF documents stay CRLF. Only column-0 keys are matched, so a
|
|
34
|
+
* nested `status:` or a body line starting with `status:` is never touched.
|
|
35
|
+
*
|
|
36
|
+
* A byte-level editor can still be wrong in a case nobody thought of, so {@link verifyFrontmatterEdit}
|
|
37
|
+
* re-parses the result; the transition verbs refuse to write when it reports a problem.
|
|
38
|
+
*/
|
|
39
|
+
const util_1 = require("util");
|
|
40
|
+
const storage_1 = require("../storage");
|
|
41
|
+
const validation_1 = require("../validation");
|
|
42
|
+
/** Split `content` into the text before the frontmatter lines, those lines, and everything after them. */
|
|
43
|
+
function locateFrontmatter(content) {
|
|
44
|
+
const { frontmatter } = (0, storage_1.splitFrontmatter)(content);
|
|
45
|
+
if (frontmatter === null) {
|
|
46
|
+
throw new Error('document has no frontmatter block');
|
|
47
|
+
}
|
|
48
|
+
// `splitFrontmatter` anchors on an opening `---` line, so the frontmatter starts after the first newline.
|
|
49
|
+
const start = content.indexOf('\n') + 1;
|
|
50
|
+
return { before: content.slice(0, start), lines: frontmatter.split('\n'), after: content.slice(start + frontmatter.length) };
|
|
51
|
+
}
|
|
52
|
+
// Lines keep a CRLF document's `\r`; every predicate below treats it as trailing whitespace, and the
|
|
53
|
+
// value scan stops before it, so an edited line keeps its ending (see `setFrontmatterField`).
|
|
54
|
+
/**
|
|
55
|
+
* Join edited frontmatter lines back between `before` and `after`. In a CRLF document the last
|
|
56
|
+
* frontmatter line's `\r` lives in `after` (`\r\n---`), so a line that becomes last after an edit must
|
|
57
|
+
* not keep its own `\r`, and a line that stops being last needs one.
|
|
58
|
+
*/
|
|
59
|
+
function assemble(before, lines, after) {
|
|
60
|
+
const crlf = before.endsWith('\r\n');
|
|
61
|
+
const joined = lines.map((line, i) => (crlf && i < lines.length - 1 && !line.endsWith('\r') ? `${line}\r` : line)).join('\n');
|
|
62
|
+
return `${before}${crlf ? joined.replace(/\r$/, '') : joined}${after}`;
|
|
63
|
+
}
|
|
64
|
+
const indentOf = (text) => text.length - text.trimStart().length;
|
|
65
|
+
const isBlank = (text) => text.trim().length === 0;
|
|
66
|
+
const isComment = (text) => text.trimStart().startsWith('#');
|
|
67
|
+
/** Index of the column-0 `key:` line (followed by a space, a tab or the end of the line), or `-1`. */
|
|
68
|
+
function findKeyLine(lines, key) {
|
|
69
|
+
return lines.findIndex((text) => {
|
|
70
|
+
const next = text.charAt(key.length + 1);
|
|
71
|
+
return text.startsWith(`${key}:`) && (next === '' || next === ' ' || next === '\t' || next === '\r');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Offset of the closing quote in `text` from `from`, honouring `\"` escapes and `''` doubling; `-1` if none. */
|
|
75
|
+
function findClosingQuote(text, from, quote) {
|
|
76
|
+
for (let i = from; i < text.length; i += 1) {
|
|
77
|
+
if (quote === '"' && text[i] === '\\') {
|
|
78
|
+
i += 1;
|
|
79
|
+
}
|
|
80
|
+
else if (text[i] === quote) {
|
|
81
|
+
if (quote === "'" && text[i + 1] === "'") {
|
|
82
|
+
i += 1;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
return i;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The value written on a `key:` line: its text, the offset right after it (where any spaces +
|
|
93
|
+
* `# comment` begin), and whether it is complete on this line (`false` for a quoted scalar whose
|
|
94
|
+
* closing quote is on a later line). In a plain value a `#` starts a comment only after whitespace,
|
|
95
|
+
* so `dr#aft` is one value.
|
|
96
|
+
*/
|
|
97
|
+
function scanHeaderValue(text, key) {
|
|
98
|
+
let start = key.length + 1;
|
|
99
|
+
while (text[start] === ' ' || text[start] === '\t')
|
|
100
|
+
start += 1;
|
|
101
|
+
const quote = text[start];
|
|
102
|
+
if (quote === '"' || quote === "'") {
|
|
103
|
+
const close = findClosingQuote(text, start + 1, quote);
|
|
104
|
+
return close === -1
|
|
105
|
+
? { value: text.slice(start), valueEnd: text.length, closed: false }
|
|
106
|
+
: { value: text.slice(start, close + 1), valueEnd: close + 1, closed: true };
|
|
107
|
+
}
|
|
108
|
+
const comment = quote === '#' ? { index: 0 } : /[ \t]#/.exec(text.slice(start));
|
|
109
|
+
const end = start + (comment ? comment.index : text.length - start);
|
|
110
|
+
const valueEnd = start + text.slice(start, end).trimEnd().length;
|
|
111
|
+
return { value: text.slice(start, valueEnd), valueEnd, closed: true };
|
|
112
|
+
}
|
|
113
|
+
/** The exclusive end index of the entry whose `key:` line is at `index` (rules in the module doc). */
|
|
114
|
+
function entryEnd(lines, index, key) {
|
|
115
|
+
const header = scanHeaderValue(lines[index], key);
|
|
116
|
+
const start = index + 1;
|
|
117
|
+
const block = /^[|>](?:([1-9])[+-]?|[+-]([1-9])?)?$/.exec(header.value);
|
|
118
|
+
if (block) {
|
|
119
|
+
const explicit = block[1] ?? block[2];
|
|
120
|
+
const firstContent = lines.slice(start).find((text) => !isBlank(text));
|
|
121
|
+
const contentIndent = explicit !== undefined ? Number(explicit) : indentOf(firstContent ?? '');
|
|
122
|
+
let end = start;
|
|
123
|
+
let lastContent = start;
|
|
124
|
+
while (contentIndent > 0 && end < lines.length) {
|
|
125
|
+
const text = lines[end];
|
|
126
|
+
if (!isBlank(text) && indentOf(text) < contentIndent)
|
|
127
|
+
break;
|
|
128
|
+
end += 1;
|
|
129
|
+
if (!isBlank(text))
|
|
130
|
+
lastContent = end;
|
|
131
|
+
}
|
|
132
|
+
return lastContent;
|
|
133
|
+
}
|
|
134
|
+
if (!header.closed) {
|
|
135
|
+
const quote = header.value.charAt(0);
|
|
136
|
+
const closing = lines.slice(start).findIndex((text) => findClosingQuote(text, 0, quote) !== -1);
|
|
137
|
+
return closing === -1 ? lines.length : start + closing + 1;
|
|
138
|
+
}
|
|
139
|
+
const nested = header.value.length === 0;
|
|
140
|
+
let lastContent = start;
|
|
141
|
+
for (let end = start; end < lines.length; end += 1) {
|
|
142
|
+
const text = lines[end];
|
|
143
|
+
// Inside a nested value a comment line continues the block whatever its indentation: a column-0
|
|
144
|
+
// `#` between two column-0 sequence items is part of that sequence, not the end of the entry
|
|
145
|
+
// (`bug-041` G2, whose removal left `- b` orphaned behind the deleted key — invalid YAML).
|
|
146
|
+
// `lastContent` still advances only on real content, so a comment that *ends* the value is not
|
|
147
|
+
// swallowed: it belongs to the parent mapping and is kept.
|
|
148
|
+
if (isBlank(text) || (nested && isComment(text)))
|
|
149
|
+
continue;
|
|
150
|
+
const child = indentOf(text) > 0 || (nested && text.startsWith('-'));
|
|
151
|
+
if (!child || isComment(text))
|
|
152
|
+
break;
|
|
153
|
+
lastContent = end + 1;
|
|
154
|
+
}
|
|
155
|
+
return lastContent;
|
|
156
|
+
}
|
|
157
|
+
/** State-machine identifiers and other simple tokens YAML reads back as the same string, unquoted. */
|
|
158
|
+
const PLAIN_SAFE = /^[A-Za-z][A-Za-z0-9._/-]*$/;
|
|
159
|
+
const YAML_TYPED_WORD = /^(?:true|false|yes|no|on|off|y|n|null)$/i;
|
|
160
|
+
/**
|
|
161
|
+
* Serialize `value` as a YAML scalar that parses back to exactly `value`: a plain token when that is
|
|
162
|
+
* unambiguous (so `status: pending` stays a one-token diff), otherwise a JSON string literal — every
|
|
163
|
+
* JSON string escape is also a valid YAML double-quoted escape, so quotes, colons, `#`, newlines and
|
|
164
|
+
* leading/trailing whitespace all survive (task-047's `rejection_reason` carries arbitrary text).
|
|
165
|
+
*/
|
|
166
|
+
function toYamlScalar(value) {
|
|
167
|
+
return PLAIN_SAFE.test(value) && !YAML_TYPED_WORD.test(value) ? value : JSON.stringify(value);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Set top-level `key` to the string `value`, serialized YAML-safely. An existing entry is replaced as a
|
|
171
|
+
* whole — continuation lines, block-scalar body or multi-line quoted value included. A `# comment` on
|
|
172
|
+
* the key line is kept when the old value ended on that line; the line ending of the key line is kept.
|
|
173
|
+
* An absent key is appended after the last non-blank frontmatter line, with the document's line ending.
|
|
174
|
+
*
|
|
175
|
+
* @throws `Error` when the document has no frontmatter block.
|
|
176
|
+
*/
|
|
177
|
+
function setFrontmatterField(content, key, value) {
|
|
178
|
+
const { before, lines, after } = locateFrontmatter(content);
|
|
179
|
+
const entry = `${key}: ${toYamlScalar(value)}`;
|
|
180
|
+
const index = findKeyLine(lines, key);
|
|
181
|
+
if (index === -1) {
|
|
182
|
+
// After the last NON-BLANK line, not simply last: trailing blank lines can belong to a
|
|
183
|
+
// keep-chomped (`|+`) block scalar's value, and pushing the new key past them would change that
|
|
184
|
+
// other field — which the post-condition then rightly refuses (`bug-041` G3).
|
|
185
|
+
let at = lines.length;
|
|
186
|
+
while (at > 0 && isBlank(lines[at - 1]))
|
|
187
|
+
at -= 1;
|
|
188
|
+
return assemble(before, [...lines.slice(0, at), entry, ...lines.slice(at)], after);
|
|
189
|
+
}
|
|
190
|
+
const header = lines[index];
|
|
191
|
+
const { valueEnd, closed } = scanHeaderValue(header, key);
|
|
192
|
+
// A value that ended on the key line keeps what followed it there (spaces, `# comment`); line
|
|
193
|
+
// endings are restored by `assemble`. A tail that starts at the `#` itself — the shape an EMPTY
|
|
194
|
+
// value with an inline comment leaves behind (`rejection_reason: # set by memory.reject`) — gets
|
|
195
|
+
// a separating space: `value# comment` is a comment YAML requires to be preceded by whitespace, and
|
|
196
|
+
// js-yaml's tolerance of it hid the defect from the re-parse post-condition (`bug-041` G1).
|
|
197
|
+
const tail = closed ? header.slice(valueEnd) : '';
|
|
198
|
+
const separated = tail.startsWith('#') ? ` ${tail}` : tail;
|
|
199
|
+
return assemble(before, [...lines.slice(0, index), `${entry}${separated}`, ...lines.slice(entryEnd(lines, index, key))], after);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Remove top-level `key` together with every line its value spans (rules in the module doc). Blank
|
|
203
|
+
* lines and comments that follow the value belong to the parent mapping and are kept. Returns `content`
|
|
204
|
+
* unchanged when the key is absent.
|
|
205
|
+
*
|
|
206
|
+
* @throws `Error` when the document has no frontmatter block.
|
|
207
|
+
*/
|
|
208
|
+
function removeFrontmatterField(content, key) {
|
|
209
|
+
const { before, lines, after } = locateFrontmatter(content);
|
|
210
|
+
const index = findKeyLine(lines, key);
|
|
211
|
+
if (index === -1)
|
|
212
|
+
return content;
|
|
213
|
+
return assemble(before, [...lines.slice(0, index), ...lines.slice(entryEnd(lines, index, key))], after);
|
|
214
|
+
}
|
|
215
|
+
/** Parse a document's frontmatter into a plain record, or return why it cannot be. */
|
|
216
|
+
function parseFrontmatter(content) {
|
|
217
|
+
const { frontmatter } = (0, storage_1.splitFrontmatter)(content);
|
|
218
|
+
if (frontmatter === null)
|
|
219
|
+
return 'rendered document has no frontmatter block';
|
|
220
|
+
try {
|
|
221
|
+
const parsed = (0, validation_1.parseYaml)(frontmatter, 'frontmatter');
|
|
222
|
+
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (!(error instanceof validation_1.ValidationError))
|
|
226
|
+
throw error;
|
|
227
|
+
return `rendered frontmatter is not valid YAML: ${error.issues.map((issue) => issue.message).join('; ')}`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The post-condition of a frontmatter edit, checked by re-parsing rather than by trusting the editor.
|
|
232
|
+
* `expected` maps each field the operation owns to its required value (`undefined` = must be absent).
|
|
233
|
+
* Returns the problems found, empty when the edit is sound:
|
|
234
|
+
*
|
|
235
|
+
* - the rendered document has no frontmatter, or it does not parse;
|
|
236
|
+
* - an owned field does not have its expected value, or is still present when it should be removed;
|
|
237
|
+
* - any **other** field's parsed value differs from `before` — an edit leaked into a field it does not own.
|
|
238
|
+
*
|
|
239
|
+
* Deterministic (REQ-SYS-07): owned fields in `expected`'s order, then other fields sorted by name.
|
|
240
|
+
*/
|
|
241
|
+
function verifyFrontmatterEdit(before, after, expected) {
|
|
242
|
+
const rendered = parseFrontmatter(after);
|
|
243
|
+
if (typeof rendered === 'string')
|
|
244
|
+
return [rendered];
|
|
245
|
+
const parsedBefore = parseFrontmatter(before);
|
|
246
|
+
const original = typeof parsedBefore === 'string' ? {} : parsedBefore;
|
|
247
|
+
return [...ownedFieldProblems(rendered, expected), ...unownedFieldProblems(original, rendered, expected)];
|
|
248
|
+
}
|
|
249
|
+
/** Each field the operation owns that does not hold its expected value (`undefined` = must be absent). */
|
|
250
|
+
function ownedFieldProblems(rendered, expected) {
|
|
251
|
+
const problems = [];
|
|
252
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
253
|
+
if (value === undefined) {
|
|
254
|
+
if (field in rendered)
|
|
255
|
+
problems.push(`field '${field}' is still present, expected it removed`);
|
|
256
|
+
}
|
|
257
|
+
else if (rendered[field] !== value) {
|
|
258
|
+
problems.push(`field '${field}' is ${JSON.stringify(rendered[field])}, expected ${JSON.stringify(value)}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return problems;
|
|
262
|
+
}
|
|
263
|
+
/** Each field the operation does NOT own whose parsed value moved. Sorted by name (REQ-SYS-07). */
|
|
264
|
+
function unownedFieldProblems(original, rendered, expected) {
|
|
265
|
+
const problems = [];
|
|
266
|
+
const others = [...new Set([...Object.keys(original), ...Object.keys(rendered)])].filter((field) => !(field in expected)).sort();
|
|
267
|
+
for (const field of others) {
|
|
268
|
+
if (!(0, util_1.isDeepStrictEqual)(original[field], rendered[field])) {
|
|
269
|
+
problems.push(`field '${field}' changed although this operation does not own it`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return problems;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The post-condition of a whole-document edit: {@link verifyFrontmatterEdit}'s frontmatter rules plus
|
|
276
|
+
* the body, gated by `scope`. Returns the problems found, empty when the edit is within scope.
|
|
277
|
+
*
|
|
278
|
+
* Under `carries-content` only the `expected` fields are checked — every other field and the body are
|
|
279
|
+
* the operation's to write. Under `declared-fields-only` an unowned field or a changed body is a
|
|
280
|
+
* problem, which is the rule `bug-076` showed nothing was enforcing.
|
|
281
|
+
*
|
|
282
|
+
* Deterministic (REQ-SYS-07): owned fields in `expected`'s order, then other fields sorted by name,
|
|
283
|
+
* then the body.
|
|
284
|
+
*/
|
|
285
|
+
function verifyDocumentEdit(before, after, expected, scope) {
|
|
286
|
+
const rendered = parseFrontmatter(after);
|
|
287
|
+
if (typeof rendered === 'string')
|
|
288
|
+
return [rendered];
|
|
289
|
+
const problems = ownedFieldProblems(rendered, expected);
|
|
290
|
+
if (scope === 'carries-content')
|
|
291
|
+
return problems;
|
|
292
|
+
const parsedBefore = parseFrontmatter(before);
|
|
293
|
+
problems.push(...unownedFieldProblems(typeof parsedBefore === 'string' ? {} : parsedBefore, rendered, expected));
|
|
294
|
+
if ((0, storage_1.splitFrontmatter)(before).body !== (0, storage_1.splitFrontmatter)(after).body) {
|
|
295
|
+
problems.push('the body changed although this operation does not own it');
|
|
296
|
+
}
|
|
297
|
+
return problems;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Name, in a form a person can act on, every way `after` differs from `before` — the frontmatter
|
|
301
|
+
* fields whose parsed value moved, and whether the body moved. `before` is `null` when the document
|
|
302
|
+
* does not exist at the revision being compared against.
|
|
303
|
+
*
|
|
304
|
+
* This is the *reporting* half of {@link verifyDocumentEdit}: the verifier answers "is this edit in
|
|
305
|
+
* scope", this answers "so what exactly is in the way". A difference that changes no parsed value and
|
|
306
|
+
* no body byte — a reordered comment, a requoted scalar — is still a difference git will commit, so
|
|
307
|
+
* it is reported as such rather than dropped.
|
|
308
|
+
*
|
|
309
|
+
* Deterministic (REQ-SYS-07): fields sorted by name, then the frontmatter-text fallback, then the body.
|
|
310
|
+
*/
|
|
311
|
+
function describeDocumentChanges(before, after) {
|
|
312
|
+
if (before === null)
|
|
313
|
+
return ['the document is not tracked at HEAD'];
|
|
314
|
+
if (before === after)
|
|
315
|
+
return [];
|
|
316
|
+
const parsedBefore = parseFrontmatter(before);
|
|
317
|
+
const parsedAfter = parseFrontmatter(after);
|
|
318
|
+
const original = typeof parsedBefore === 'string' ? {} : parsedBefore;
|
|
319
|
+
const rendered = typeof parsedAfter === 'string' ? {} : parsedAfter;
|
|
320
|
+
const changes = [...new Set([...Object.keys(original), ...Object.keys(rendered)])]
|
|
321
|
+
.sort()
|
|
322
|
+
.filter((field) => !(0, util_1.isDeepStrictEqual)(original[field], rendered[field]))
|
|
323
|
+
.map((field) => `frontmatter field '${field}'`);
|
|
324
|
+
const beforeSplit = (0, storage_1.splitFrontmatter)(before);
|
|
325
|
+
const afterSplit = (0, storage_1.splitFrontmatter)(after);
|
|
326
|
+
if (changes.length === 0 && beforeSplit.frontmatter !== afterSplit.frontmatter) {
|
|
327
|
+
changes.push('the frontmatter text (comments or formatting)');
|
|
328
|
+
}
|
|
329
|
+
if (beforeSplit.body !== afterSplit.body)
|
|
330
|
+
changes.push('the body');
|
|
331
|
+
return changes.length > 0 ? changes : ['the file content'];
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=frontmatter-edit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-edit.js","sourceRoot":"","sources":["../../src/memory/frontmatter-edit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `git log <extraArgs> --format=<each field followed by %x00> -- <pathspecs>` and return each
|
|
3
|
+
* matching commit's raw field list (in `fields` order, not yet destructured into a named shape —
|
|
4
|
+
* that's each caller's concern), oldest first.
|
|
5
|
+
*
|
|
6
|
+
* Because the field and record delimiters are the same character ({@link NUL}), records are
|
|
7
|
+
* recovered by **arity** rather than by a second delimiter: the stream is split on NUL and consumed
|
|
8
|
+
* in fixed groups of `fields.length`. That is what makes the walk independent of its own output's
|
|
9
|
+
* content — there is no character a commit could carry that would realign the groups — and it also
|
|
10
|
+
* means a field that is legitimately empty (a commit with no body, `%b`) still occupies its slot
|
|
11
|
+
* instead of being dropped. **Every returned record therefore has exactly `fields.length` entries**
|
|
12
|
+
* — a partial group at the end of the stream is not a record and is discarded, never padded — which
|
|
13
|
+
* is the postcondition `./history`'s destructuring relies on.
|
|
14
|
+
*
|
|
15
|
+
* git terminates each commit's formatted output with a newline. The format ends with a delimiter, so
|
|
16
|
+
* that newline falls in the piece AFTER the record's last field — which is why the split's final
|
|
17
|
+
* piece is dropped rather than grouped. Dropping it explicitly, instead of letting it fall off as a
|
|
18
|
+
* short remainder, is what makes the walk total at **one** field too: at that arity the tail is a
|
|
19
|
+
* whole group of its own and became a spurious record (it did, on this task's first pass; the
|
|
20
|
+
* arity-1 cases in `test/memory/git-log-framing.test.ts` now pin it). What survives of the newline is
|
|
21
|
+
* a single leading `\n` on each record's first field after the first, and that is stripped; both call
|
|
22
|
+
* sites put `%H` first, which never legitimately begins with one.
|
|
23
|
+
*
|
|
24
|
+
* Returns `[]` — never throws — both when `root` is not a git repository at all and when none of
|
|
25
|
+
* `pathspecs` has any matching history; "not a repo"/"not found" is a caller concern, not this
|
|
26
|
+
* primitive's (mirrors `getMemoryHistory`'s original contract). That now holds at EVERY arity: with
|
|
27
|
+
* no matching commits git prints nothing, the split yields one piece, and dropping it leaves nothing
|
|
28
|
+
* to group. An empty `fields` requests nothing and likewise yields `[]`, rather than looping forever
|
|
29
|
+
* over zero-width groups.
|
|
30
|
+
*/
|
|
31
|
+
export declare function walkGitLogFields(root: string, fields: readonly string[], pathspecs: readonly string[], extraArgs?: readonly string[]): string[][];
|
|
32
|
+
//# sourceMappingURL=git-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-log.d.ts","sourceRoot":"","sources":["../../src/memory/git-log.ts"],"names":[],"mappings":"AA2CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,SAAS,GAAE,SAAS,MAAM,EAAO,GAChC,MAAM,EAAE,EAAE,CA6BZ"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.walkGitLogFields = walkGitLogFields;
|
|
4
|
+
/**
|
|
5
|
+
* Shared low-level `git log` field walk (task-015-complete-audit-trail refactor) — task-011's
|
|
6
|
+
* `./history` (`getMemoryHistory`) and this task's `./audit` (`auditAttribution`) both need the same
|
|
7
|
+
* "run `git log` with a custom multi-field format, split stdout into per-commit records, oldest
|
|
8
|
+
* first" plumbing over a different field set/pathspec shape; this is that one shared primitive, so
|
|
9
|
+
* the record-separator convention lives in exactly one place instead of being copy-pasted twice.
|
|
10
|
+
*/
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
/**
|
|
13
|
+
* The `--format` placeholder git expands to a single NUL byte, used to delimit BOTH the fields of a
|
|
14
|
+
* commit and one commit's record from the next. A literal NUL cannot be written into the format
|
|
15
|
+
* string, because it travels to git through `argv` and a C string ends at the first NUL — so `%x00`
|
|
16
|
+
* is the only way to get one there at all.
|
|
17
|
+
*/
|
|
18
|
+
const NUL_PLACEHOLDER = '%x00';
|
|
19
|
+
/**
|
|
20
|
+
* The delimiter {@link NUL_PLACEHOLDER} expands to, and the one character this module splits on.
|
|
21
|
+
*
|
|
22
|
+
* The guarantee, stated as narrowly as it actually holds
|
|
23
|
+
* (`task-086-fix-reason-control-chars-history-forgery`, `bug-050`): **git refuses to write a commit
|
|
24
|
+
* whose message contains a NUL byte** — `error: a NUL byte in commit log message not allowed`,
|
|
25
|
+
* raised by every writer down to `commit-tree`, the lowest-level plumbing there is. This is not a
|
|
26
|
+
* claim about what commit text conventionally contains; it is a property git enforces at write time,
|
|
27
|
+
* and it is the same property git's own `-z` output options rest on.
|
|
28
|
+
* `test/memory/git-log-framing.test.ts` pins it against all three writers rather than trusting it.
|
|
29
|
+
*
|
|
30
|
+
* It replaced a pair of separators (`0x1f`/`0x1e`) documented as characters "a real commit
|
|
31
|
+
* subject/body never contains". That claim was false by construction: `--reason <text>` is arbitrary
|
|
32
|
+
* free text that lands in the commit body (P1.7/P1.8, REQ-SEC-04), so a reason carrying `0x1e` split
|
|
33
|
+
* one commit's record in two and made `wingfoil memory history` (P1.10) print a fabricated entry
|
|
34
|
+
* whose `sha` was the caller-supplied text, while the genuine approval lost its `from`. Because the
|
|
35
|
+
* delimiters live in the `--format` string — expanded by git at READ time and never stored — moving
|
|
36
|
+
* to NUL re-reads all history under the new framing, whenever and by whichever writer it was made.
|
|
37
|
+
*
|
|
38
|
+
* Built with `String.fromCharCode` rather than spelled as a unicode escape for code point zero,
|
|
39
|
+
* because any tool that resolves such an escape while editing this file leaves a RAW NUL byte in
|
|
40
|
+
* the source — at which point git classifies the file as binary and stops diffing it. Observed
|
|
41
|
+
* once here, hence the note: the constructor form cannot be resolved into the file by accident.
|
|
42
|
+
*/
|
|
43
|
+
const NUL = String.fromCharCode(0);
|
|
44
|
+
/**
|
|
45
|
+
* Run `git log <extraArgs> --format=<each field followed by %x00> -- <pathspecs>` and return each
|
|
46
|
+
* matching commit's raw field list (in `fields` order, not yet destructured into a named shape —
|
|
47
|
+
* that's each caller's concern), oldest first.
|
|
48
|
+
*
|
|
49
|
+
* Because the field and record delimiters are the same character ({@link NUL}), records are
|
|
50
|
+
* recovered by **arity** rather than by a second delimiter: the stream is split on NUL and consumed
|
|
51
|
+
* in fixed groups of `fields.length`. That is what makes the walk independent of its own output's
|
|
52
|
+
* content — there is no character a commit could carry that would realign the groups — and it also
|
|
53
|
+
* means a field that is legitimately empty (a commit with no body, `%b`) still occupies its slot
|
|
54
|
+
* instead of being dropped. **Every returned record therefore has exactly `fields.length` entries**
|
|
55
|
+
* — a partial group at the end of the stream is not a record and is discarded, never padded — which
|
|
56
|
+
* is the postcondition `./history`'s destructuring relies on.
|
|
57
|
+
*
|
|
58
|
+
* git terminates each commit's formatted output with a newline. The format ends with a delimiter, so
|
|
59
|
+
* that newline falls in the piece AFTER the record's last field — which is why the split's final
|
|
60
|
+
* piece is dropped rather than grouped. Dropping it explicitly, instead of letting it fall off as a
|
|
61
|
+
* short remainder, is what makes the walk total at **one** field too: at that arity the tail is a
|
|
62
|
+
* whole group of its own and became a spurious record (it did, on this task's first pass; the
|
|
63
|
+
* arity-1 cases in `test/memory/git-log-framing.test.ts` now pin it). What survives of the newline is
|
|
64
|
+
* a single leading `\n` on each record's first field after the first, and that is stripped; both call
|
|
65
|
+
* sites put `%H` first, which never legitimately begins with one.
|
|
66
|
+
*
|
|
67
|
+
* Returns `[]` — never throws — both when `root` is not a git repository at all and when none of
|
|
68
|
+
* `pathspecs` has any matching history; "not a repo"/"not found" is a caller concern, not this
|
|
69
|
+
* primitive's (mirrors `getMemoryHistory`'s original contract). That now holds at EVERY arity: with
|
|
70
|
+
* no matching commits git prints nothing, the split yields one piece, and dropping it leaves nothing
|
|
71
|
+
* to group. An empty `fields` requests nothing and likewise yields `[]`, rather than looping forever
|
|
72
|
+
* over zero-width groups.
|
|
73
|
+
*/
|
|
74
|
+
function walkGitLogFields(root, fields, pathspecs, extraArgs = []) {
|
|
75
|
+
if (fields.length === 0)
|
|
76
|
+
return [];
|
|
77
|
+
const format = fields.map((field) => `${field}${NUL_PLACEHOLDER}`).join('');
|
|
78
|
+
let stdout;
|
|
79
|
+
try {
|
|
80
|
+
stdout = (0, child_process_1.execFileSync)('git', ['-C', root, 'log', ...extraArgs, `--format=${format}`, '--', ...pathspecs], { encoding: 'utf-8' });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
// The stream is `<f1>NUL<f2>NUL…<fn>NUL` per commit, each run terminated by git's own newline, so
|
|
86
|
+
// the text after the FINAL NUL is never a field — it is that newline, or the empty string when git
|
|
87
|
+
// printed nothing at all. Drop it before grouping; at `fields.length === 1` it would otherwise be
|
|
88
|
+
// a complete group and become a record that no commit backs. Only whole groups of `fields.length`
|
|
89
|
+
// are records thereafter.
|
|
90
|
+
const pieces = stdout.split(NUL);
|
|
91
|
+
pieces.pop();
|
|
92
|
+
const records = [];
|
|
93
|
+
for (let index = 0; index + fields.length <= pieces.length; index += fields.length) {
|
|
94
|
+
const record = pieces.slice(index, index + fields.length);
|
|
95
|
+
record[0] = record[0].replace(/^\n+/, '');
|
|
96
|
+
records.push(record);
|
|
97
|
+
}
|
|
98
|
+
return records.reverse();
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=git-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-log.js","sourceRoot":"","sources":["../../src/memory/git-log.ts"],"names":[],"mappings":";;AAyEA,4CAkCC;AA3GD;;;;;;GAMG;AACH,iDAA6C;AAE7C;;;;;GAKG;AACH,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,SAAgB,gBAAgB,CAC9B,IAAY,EACZ,MAAyB,EACzB,SAA4B,EAC5B,YAA+B,EAAE;IAEjC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAA,4BAAY,EACnB,KAAK,EACL,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,YAAY,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,EAC3E,EAAE,QAAQ,EAAE,OAAO,EAAE,CACtB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,kGAAkG;IAClG,mGAAmG;IACnG,kGAAkG;IAClG,kGAAkG;IAClG,0BAA0B;IAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,EAAE,CAAC;IACb,MAAM,OAAO,GAAe,EAAE,CAAC;IAC/B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QACnF,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC1D,MAAM,CAAC,CAAC,CAAC,GAAI,MAAM,CAAC,CAAC,CAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/** One commit touching a Memory document, in the shape `wingfoil memory history` will render from. */
|
|
2
|
+
export interface MemoryHistoryEntry {
|
|
3
|
+
readonly sha: string;
|
|
4
|
+
readonly authorName: string;
|
|
5
|
+
readonly authorEmail: string;
|
|
6
|
+
/** Author date, ISO-8601 (`git log --format=%aI`) — never the commit/system date. */
|
|
7
|
+
readonly date: string;
|
|
8
|
+
readonly subject: string;
|
|
9
|
+
readonly body: string;
|
|
10
|
+
/**
|
|
11
|
+
* The root-relative path the document occupied **at this commit**, which is not in general the
|
|
12
|
+
* path the caller asked for: the walk follows renames, so a commit older than one holds the
|
|
13
|
+
* element under its former name ({@link collectHistoricalPaths}).
|
|
14
|
+
*
|
|
15
|
+
* Carried on the entry rather than recomputed by each consumer, because the walk is the only thing
|
|
16
|
+
* that knows it — `git log --follow --name-status` reports every `R<score> <old> <new>` edge it
|
|
17
|
+
* crosses. A reader that instead asks git for the CURRENT path at every sha gets nothing back at
|
|
18
|
+
* the pre-rename ones, which is
|
|
19
|
+
* `bug-080-read-status-at-reads-the-current-path-at-pre-rename-commits`
|
|
20
|
+
* (`task-097-memory-history-reads-each-commit-at-its-historical-path`).
|
|
21
|
+
*/
|
|
22
|
+
readonly path: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The sha of the commit that introduced `relativePath` as a COPY of a file that outlives it — the
|
|
26
|
+
* element's own creation — or `null` when the followed chain contains no copy edge at all.
|
|
27
|
+
*
|
|
28
|
+
* This is the distinction the fix turns on. `git log --follow` can change path across two different
|
|
29
|
+
* kinds of edge, and they mean opposite things for an audit trail:
|
|
30
|
+
*
|
|
31
|
+
* - **`R` (rename)** — the element continuing under a new name. Keep following. This is not a
|
|
32
|
+
* hypothetical requirement: `docs/self/docs/04_memory/planning/v1/*` became `planning/rl-v1/*` in
|
|
33
|
+
* one commit in this repository, moving five `release` elements at once, because the `release`
|
|
34
|
+
* type's `path` pattern interpolates the release-line id (`memory.yaml`). For one of them a plain
|
|
35
|
+
* `git log -- <path>` returns a single commit where the followed walk returns seven, so dropping
|
|
36
|
+
* `--follow` would delete five sixths of that element's recorded history.
|
|
37
|
+
* - **`C` (copy)** — the element being born out of a file that still exists. `wingfoil memory add`
|
|
38
|
+
* copies the type's template verbatim (CLAUDE.md §5.1), and `wingfoil init` has already committed
|
|
39
|
+
* that template, so this edge is how every scaffolded element begins. Everything strictly older
|
|
40
|
+
* than it is the template's history, not the element's.
|
|
41
|
+
*
|
|
42
|
+
* Only the newest copy edge matters — it is the element's own creation; any older one belongs to
|
|
43
|
+
* whatever the source itself was copied from. `git log` prints newest first, so that is the first
|
|
44
|
+
* line.
|
|
45
|
+
*
|
|
46
|
+
* **Throws** — never returns `null` — when git itself fails. "git could not answer" and "this
|
|
47
|
+
* element was never copied from anything" are different answers, and collapsing the first into the
|
|
48
|
+
* second would silently restore the phantom entry; it is also why this cannot borrow
|
|
49
|
+
* `walkGitLogFields`, whose `catch` returns `[]` (`bug-072-oversized-git-log-becomes-empty-history`).
|
|
50
|
+
* {@link getMemoryHistory} calls this only once the main walk has already returned commits, so at
|
|
51
|
+
* that point the repository and the path are both known good and a failure here is genuinely
|
|
52
|
+
* exceptional.
|
|
53
|
+
*
|
|
54
|
+
* @param root - Absolute path of the repository to walk.
|
|
55
|
+
* @param relativePath - The element's root-relative path, as {@link getMemoryHistory} takes it.
|
|
56
|
+
* @returns The creation commit's full sha, or `null` when the chain has no copy edge.
|
|
57
|
+
*/
|
|
58
|
+
export declare function findElementCreationSha(root: string, relativePath: string): string | null;
|
|
59
|
+
/**
|
|
60
|
+
* Drop the part of an oldest-first walk that precedes the element's creation, keeping the creation
|
|
61
|
+
* commit itself — the ancestry `--follow` reached through a copy edge, which belongs to the file the
|
|
62
|
+
* element was copied from. `creationSha` of `null` (no copy edge: the document was authored in
|
|
63
|
+
* place) leaves the walk untouched.
|
|
64
|
+
*
|
|
65
|
+
* Pure: no git, no filesystem. Separated from {@link findElementCreationSha} so the truncation rule
|
|
66
|
+
* and the invariant below are exercised directly rather than through a repository fixture.
|
|
67
|
+
*
|
|
68
|
+
* **Throws** when `creationSha` is absent from `entries`. The two walks are the same walk — one is
|
|
69
|
+
* the other filtered by git — so a sha in the narrower one that is missing from the wider one is a
|
|
70
|
+
* broken invariant, not a case to absorb. Defaulting to "no truncation" there would report the
|
|
71
|
+
* phantom entry again while reporting success.
|
|
72
|
+
*
|
|
73
|
+
* @param entries - The full `--follow` walk, oldest first.
|
|
74
|
+
* @param creationSha - The element's creation commit, from {@link findElementCreationSha}.
|
|
75
|
+
* @returns `entries` from the creation commit onward (a new array; `entries` is not mutated).
|
|
76
|
+
*/
|
|
77
|
+
export declare function dropPreCreationAncestry(entries: readonly MemoryHistoryEntry[], creationSha: string | null): MemoryHistoryEntry[];
|
|
78
|
+
/**
|
|
79
|
+
* Where `relativePath`'s document lived at each commit of its `--follow` walk, keyed by sha.
|
|
80
|
+
*
|
|
81
|
+
* `git log --follow --name-status` prints, per commit, the sha then that commit's status line for
|
|
82
|
+
* the followed path — `A <path>`, `M <path>`, `D <path>`, or `R<score> <old> <new>` at a rename. The
|
|
83
|
+
* path the file occupies **at** a commit is always the LAST tab-separated field of that line: the
|
|
84
|
+
* destination of a rename, the single path otherwise. Everything older than that rename then reports
|
|
85
|
+
* the old path, which is the whole point — it is what
|
|
86
|
+
* `git show <sha>:<path>` needs in order to resolve at all.
|
|
87
|
+
*
|
|
88
|
+
* A commit git reports no status line for (it shows no diff for a merge without `-m`) is simply
|
|
89
|
+
* absent from the map; {@link attachHistoricalPaths} decides what to do about that, so the rule is
|
|
90
|
+
* testable without a repository.
|
|
91
|
+
*
|
|
92
|
+
* **Throws** — never returns an empty map — when git itself fails, for the same reason
|
|
93
|
+
* {@link findElementCreationSha} does: "git could not answer" and "this element was never renamed"
|
|
94
|
+
* are different answers, and folding the first into the second silently reinstates `bug-080` by
|
|
95
|
+
* reading every commit at the current path again. {@link getMemoryHistory} calls this only once the
|
|
96
|
+
* main walk has returned commits, so the repository and the path are both known good by then.
|
|
97
|
+
*
|
|
98
|
+
* @param root - Absolute path of the repository to walk.
|
|
99
|
+
* @param relativePath - The element's current root-relative path, as the caller named it.
|
|
100
|
+
* @returns sha → the root-relative path the document occupied at that commit.
|
|
101
|
+
*/
|
|
102
|
+
export declare function collectHistoricalPaths(root: string, relativePath: string): Map<string, string>;
|
|
103
|
+
/**
|
|
104
|
+
* Stamp each walked commit with the path the element occupied at it, from
|
|
105
|
+
* {@link collectHistoricalPaths}' map.
|
|
106
|
+
*
|
|
107
|
+
* Pure: no git, no filesystem — separated from the probe for the same reason
|
|
108
|
+
* {@link dropPreCreationAncestry} is separated from {@link findElementCreationSha}, so the rule and
|
|
109
|
+
* its fallback are exercised directly rather than through a repository fixture.
|
|
110
|
+
*
|
|
111
|
+
* Falls back to `fallbackPath` (the path the caller named) for a sha the probe reported no diff for,
|
|
112
|
+
* rather than throwing: unlike the creation probe's invariant, an absent entry here is not evidence
|
|
113
|
+
* of a broken walk — git shows no diff for a merge commit — and the fallback is precisely the
|
|
114
|
+
* behaviour that shipped before this field existed, so it can degrade no further than the status quo.
|
|
115
|
+
*
|
|
116
|
+
* @param entries - The `--follow` walk, oldest first.
|
|
117
|
+
* @param pathBySha - sha → historical path, from {@link collectHistoricalPaths}.
|
|
118
|
+
* @param fallbackPath - The element's current root-relative path.
|
|
119
|
+
* @returns A new array; `entries` is not mutated.
|
|
120
|
+
*/
|
|
121
|
+
export declare function attachHistoricalPaths(entries: readonly MemoryHistoryEntry[], pathBySha: ReadonlyMap<string, string>, fallbackPath: string): MemoryHistoryEntry[];
|
|
122
|
+
/**
|
|
123
|
+
* Walk every commit that touched `relativePath`, oldest first (P1.10-memory-history.feature: "lists
|
|
124
|
+
* ... entries in chronological order") — `git log` itself returns newest-first, reversed by
|
|
125
|
+
* {@link walkGitLogFields} — and only those commits. The walk follows renames, so an element moved on
|
|
126
|
+
* disk keeps its history, but stops at the element's own creation rather than continuing into the
|
|
127
|
+
* template it was copied from ({@link findElementCreationSha}).
|
|
128
|
+
*
|
|
129
|
+
* Returns `[]`, rather than throwing, both when `root` has no commits touching the path at all and
|
|
130
|
+
* when `root` is not a git repository — "document not found" is a caller/feature-layer concern
|
|
131
|
+
* (task-021/026-style CLI error rendering), not this primitive's. That is also why the creation
|
|
132
|
+
* probe runs only on a non-empty walk: on an empty one there is nothing to truncate and nothing to
|
|
133
|
+
* conclude from git's silence — and the same for the path probe.
|
|
134
|
+
*
|
|
135
|
+
* Every returned entry carries the path the element occupied at its own commit
|
|
136
|
+
* ({@link MemoryHistoryEntry.path}), not the one the caller named. That is what makes this walk the
|
|
137
|
+
* single source of truth for "where was this file at this commit": a consumer that reads content per
|
|
138
|
+
* commit — `./audit`'s `reconstructMemoryTransitions`, which reads the frontmatter `status:` — must
|
|
139
|
+
* take the path from here rather than re-using its own argument (`bug-080`).
|
|
140
|
+
*/
|
|
141
|
+
export declare function getMemoryHistory(root: string, relativePath: string): MemoryHistoryEntry[];
|
|
142
|
+
//# sourceMappingURL=history.d.ts.map
|