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,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enumerateOperations = enumerateOperations;
|
|
4
|
+
exports.deriveVerb = deriveVerb;
|
|
5
|
+
exports.computeParityDiff = computeParityDiff;
|
|
6
|
+
/**
|
|
7
|
+
* Flatten `modules` into every declared operation, sorted deterministically by `(module.name,
|
|
8
|
+
* operation.name)` (REQ-SYS-07 — no unordered `Object.values`/iteration in a context-building
|
|
9
|
+
* path). Both `src/cli` and `src/mcp` register from this single enumerator, never from their own
|
|
10
|
+
* ad hoc traversal of `modules`/`Object.keys`.
|
|
11
|
+
*/
|
|
12
|
+
function enumerateOperations(modules) {
|
|
13
|
+
const out = [];
|
|
14
|
+
const sortedModules = [...modules].sort((a, b) => a.name.localeCompare(b.name));
|
|
15
|
+
for (const module of sortedModules) {
|
|
16
|
+
const operationNames = Object.keys(module.operations).sort();
|
|
17
|
+
for (const operationName of operationNames) {
|
|
18
|
+
const operation = module.operations[operationName];
|
|
19
|
+
if (operation)
|
|
20
|
+
out.push({ module, operation });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** camelCase -> kebab-case (`"setTeam"` -> `"set-team"`), used by {@link deriveVerb}. */
|
|
26
|
+
function camelToKebab(value) {
|
|
27
|
+
return value.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Derive an operation's CLI verb / MCP verb segment from its camelCase `{module}{Verb}` name
|
|
31
|
+
* (spec-006 §5), mechanically — this plus `mutates` is "the one place surface-routing logic
|
|
32
|
+
* lives" (§2): given `moduleName = "memory"` and `operationName = "memoryApprove"`, returns
|
|
33
|
+
* `"approve"`; a multi-word verb (`"workflowSetTeam"`) becomes kebab-case (`"set-team"`) so it
|
|
34
|
+
* renders as a single CLI token / Tool-name segment. Falls back to the full operation name,
|
|
35
|
+
* kebab-cased, when it does not start with the module's name — a config mistake the caller should
|
|
36
|
+
* catch earlier, but this function degrades rather than throwing, so a single bad registration
|
|
37
|
+
* cannot crash an adapter's entire registration pass.
|
|
38
|
+
*/
|
|
39
|
+
function deriveVerb(moduleName, operationName) {
|
|
40
|
+
// A "self-named" operation — its name is EXACTLY the module name (e.g. module `paths`, operation
|
|
41
|
+
// `paths`) — derives the empty string, not a verb: this is spec-008-cli-grammar §1's flat/no-verb
|
|
42
|
+
// command form (`wingfoil <noun> [args] [flags]`, e.g. `wingfoil paths [category]`), as opposed to
|
|
43
|
+
// the `<noun> <verb>` form every other operation today derives (`wingfoil dna show`). Both
|
|
44
|
+
// `src/cli/program.ts` and `src/mcp/registrar.ts` special-case an empty verb to skip the
|
|
45
|
+
// subcommand/URI-segment nesting they otherwise add (task-028-implement-paths-category).
|
|
46
|
+
if (operationName === moduleName)
|
|
47
|
+
return '';
|
|
48
|
+
const startsWithModule = operationName.startsWith(moduleName) && operationName.length > moduleName.length;
|
|
49
|
+
const suffix = startsWithModule ? operationName.slice(moduleName.length) : operationName;
|
|
50
|
+
const withLeadingLower = suffix.length > 0 ? suffix[0].toLowerCase() + suffix.slice(1) : suffix;
|
|
51
|
+
return camelToKebab(withLeadingLower);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Set-difference both ways between two enumerations of the same kind of key (e.g. `"noun verb"`
|
|
55
|
+
* CLI commands vs. the same keys' MCP Tool names, normalized to a common form by the caller) —
|
|
56
|
+
* the REQ-SYS-05 fit-criterion primitive: "reports 0 [operations] without a matching [counterpart]"
|
|
57
|
+
* in each direction. Both output lists are sorted, independent of input order (REQ-SYS-07).
|
|
58
|
+
*/
|
|
59
|
+
function computeParityDiff(a, b) {
|
|
60
|
+
const setA = new Set(a);
|
|
61
|
+
const setB = new Set(b);
|
|
62
|
+
const onlyInA = [...setA].filter((key) => !setB.has(key)).sort();
|
|
63
|
+
const onlyInB = [...setB].filter((key) => !setA.has(key)).sort();
|
|
64
|
+
return { onlyInA, onlyInB };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":";;AAiGA,kDAWC;AAiBD,gCAYC;AAyFD,8CAMC;AA7ID;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,OAA8B;IAChE,MAAM,GAAG,GAA0B,EAAE,CAAC;IACtC,MAAM,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAChF,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7D,KAAK,MAAM,aAAa,IAAI,cAAc,EAAE,CAAC;YAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;YACnD,IAAI,SAAS;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,yFAAyF;AACzF,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,UAAU,CAAC,UAAkB,EAAE,aAAqB;IAClE,iGAAiG;IACjG,kGAAkG;IAClG,mGAAmG;IACnG,2FAA2F;IAC3F,yFAAyF;IACzF,yFAAyF;IACzF,IAAI,aAAa,KAAK,UAAU;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,gBAAgB,GAAG,aAAa,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC1G,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;IACzF,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACjG,OAAO,YAAY,CAAC,gBAAgB,CAAC,CAAC;AACxC,CAAC;AAmFD;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,CAAoB,EAAE,CAAoB;IAC1E,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { MemoryYaml } from '../memory/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Caps that keep an assembled context "bounded, not a full dump" (spec-012 §6) — the defaults match
|
|
4
|
+
* spec-012's own defaults verbatim: `maxDocs: 40`, `maxBytes: 262144` (256 KiB).
|
|
5
|
+
*/
|
|
6
|
+
export interface ContextLimits {
|
|
7
|
+
/** Maximum number of Memory documents included. */
|
|
8
|
+
readonly maxDocs: number;
|
|
9
|
+
/** Maximum total Memory-document body bytes (UTF-8) included. */
|
|
10
|
+
readonly maxBytes: number;
|
|
11
|
+
}
|
|
12
|
+
/** spec-012 §6's default {@link ContextLimits} (`maxDocs: 40`, `maxBytes: 262144` / 256 KiB). */
|
|
13
|
+
export declare const DEFAULT_CONTEXT_LIMITS: ContextLimits;
|
|
14
|
+
/**
|
|
15
|
+
* The active Memory element (usually a `task`) relevance is scored against — already resolved by the
|
|
16
|
+
* caller (spec-012 §3 stage 1, `resolve-element`, is a separate pipeline stage; this primitive is a
|
|
17
|
+
* pure `(state) -> selection` function and never reads the element document itself, per REQ-SYS-07).
|
|
18
|
+
*/
|
|
19
|
+
export interface RelevanceElementRef {
|
|
20
|
+
/** The element's `memory.yaml` type (e.g. `"task"`). */
|
|
21
|
+
readonly type: string;
|
|
22
|
+
/** The element's `id` frontmatter value. */
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** The element's already-parsed frontmatter (used to derive T1 links, T2 release scope, T3
|
|
25
|
+
* traceability keys, and T4 keyword/tag overlap — see {@link filterRelevantMemoryDocuments}). */
|
|
26
|
+
readonly frontmatter: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/** One Memory document selected as relevant, carrying the {@link ContextLimits}-bounding fields plus
|
|
29
|
+
* its computed spec-012 §6 tier `score` (exposed for callers that want to display/debug ranking). */
|
|
30
|
+
export interface RelevantMemoryDocument {
|
|
31
|
+
readonly path: string;
|
|
32
|
+
readonly type?: string;
|
|
33
|
+
readonly id?: string;
|
|
34
|
+
readonly title?: string;
|
|
35
|
+
readonly status?: string;
|
|
36
|
+
readonly frontmatter: Record<string, unknown>;
|
|
37
|
+
readonly body: string;
|
|
38
|
+
/** `1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)` (spec-012 §6) — always `> 0` for an included doc. */
|
|
39
|
+
readonly score: number;
|
|
40
|
+
}
|
|
41
|
+
/** P5.3.3-relevance-filtering.feature's "Edge - no documents pass the relevance threshold" note,
|
|
42
|
+
* verbatim: `'a note "no relevant Memory found for task" is recorded'`. */
|
|
43
|
+
export declare const NO_RELEVANT_MEMORY_NOTE = "no relevant Memory found for task";
|
|
44
|
+
/**
|
|
45
|
+
* {@link filterRelevantMemoryDocuments}'s result: the bounded, ordered set of relevant documents, plus
|
|
46
|
+
* {@link NO_RELEVANT_MEMORY_NOTE} when — and only when — **nothing passed the relevance threshold**
|
|
47
|
+
* (mirrors `memorySearchFn`'s `message`-only-when-empty convention in `src/core/index.ts`).
|
|
48
|
+
*
|
|
49
|
+
* The note is keyed to the *scored* set, never to the *bounded* one: "no relevant Memory found for
|
|
50
|
+
* task" is a factual claim, and documents that scored as relevant but did not fit inside
|
|
51
|
+
* {@link ContextLimits} were found — they were merely not carried. In that case `documents` is empty
|
|
52
|
+
* and `note` is `undefined`, because the filter cannot honestly assert the stronger statement.
|
|
53
|
+
*/
|
|
54
|
+
export interface RelevantMemoryResult {
|
|
55
|
+
readonly documents: readonly RelevantMemoryDocument[];
|
|
56
|
+
readonly note?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Rank and bound the Memory documents relevant to `element` (spec-012 §6, REQ-PERF-05). Deterministic
|
|
60
|
+
* end to end (REQ-SYS-07):
|
|
61
|
+
*
|
|
62
|
+
* 1. **Scan** every document `memoryYaml` declares under `root` ({@link listMemoryDocumentPaths}'s
|
|
63
|
+
* already-sorted order, task-008) — no second directory walk.
|
|
64
|
+
* 2. **Exclude** the element's own document (never relevant to itself) and any document
|
|
65
|
+
* {@link isExcludedFromContext} bars — `draft` plus the shared archived set `{deprecated,
|
|
66
|
+
* superseded}` (`dl-028`; the archived half is the same predicate REQ-STATE-06 applies to default
|
|
67
|
+
* `memory search`, the `draft` half is context-only).
|
|
68
|
+
* 3. **Score** each remaining document: `1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)` — T1 explicit
|
|
69
|
+
* link, T2 same release scope, T3 shared traceability key, T4 keyword/tag overlap count
|
|
70
|
+
* (spec-012 §6's exact formula). A document scoring `0` (no tier hit at all) is **not relevant**
|
|
71
|
+
* and is dropped — this is the relevance threshold P5.3.3-relevance-filtering.feature's "Edge - no
|
|
72
|
+
* documents pass the relevance threshold" scenario exercises.
|
|
73
|
+
* 4. **Order**: score DESC, then `type` ASC, then `id` ASC (falling back to `path` when a document has
|
|
74
|
+
* no `id`) — a total, deterministic tie-break (spec-012 §6/REQ-SYS-07).
|
|
75
|
+
* 5. **Bound**: walk in that order, including documents until either `limits.maxDocs` or
|
|
76
|
+
* `limits.maxBytes` (summed UTF-8 body bytes) would be exceeded, then **stop** — never skip a
|
|
77
|
+
* lower-ranked document to fit under a cap while a higher-ranked one was excluded, and never
|
|
78
|
+
* partially include a document (spec-012 §6's "deterministic truncation").
|
|
79
|
+
*
|
|
80
|
+
* Returns {@link NO_RELEVANT_MEMORY_NOTE} in `note` only when **step 3 left nothing** — i.e. no
|
|
81
|
+
* document passed the relevance threshold at all (P5.3.3's edge-case scenario, verbatim wording).
|
|
82
|
+
* When documents scored as relevant but step 5's caps admitted none of them, `documents` is empty and
|
|
83
|
+
* `note` is omitted: relevant Memory *was* found, so the note would assert something false.
|
|
84
|
+
*/
|
|
85
|
+
export declare function filterRelevantMemoryDocuments(root: string, memoryYaml: MemoryYaml, element: RelevanceElementRef, limits?: ContextLimits): RelevantMemoryResult;
|
|
86
|
+
//# sourceMappingURL=relevance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.d.ts","sourceRoot":"","sources":["../../src/core/relevance.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,EAAE,aAAiD,CAAC;AAEvF;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;qGACiG;IACjG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAED;qGACqG;AACrG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sGAAsG;IACtG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;2EAC2E;AAC3E,eAAO,MAAM,uBAAuB,sCAAsC,CAAC;AAE3E;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAoID;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,mBAAmB,EAC5B,MAAM,GAAE,aAAsC,GAC7C,oBAAoB,CAsDtB"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NO_RELEVANT_MEMORY_NOTE = exports.DEFAULT_CONTEXT_LIMITS = void 0;
|
|
4
|
+
exports.filterRelevantMemoryDocuments = filterRelevantMemoryDocuments;
|
|
5
|
+
/**
|
|
6
|
+
* `relevance-filter` (task-035-bounded-context-relevance, REQ-PERF-05) —
|
|
7
|
+
* spec-012-context-loader-relevance-filtering §6's deterministic Memory-document selection unit:
|
|
8
|
+
* rank and bound the set of Memory documents relevant to an active element (usually a `task`), so an
|
|
9
|
+
* assembled agent context stays "curated, not full dump" (Product Brief) and satisfies REQ-PERF-05's
|
|
10
|
+
* Fit Criterion verbatim — "given 1,000 Memory documents of which K are relevant to the task, the
|
|
11
|
+
* assembled context contains exactly the K relevant (non-deprecated) documents and 0 others."
|
|
12
|
+
*
|
|
13
|
+
* This module implements ONLY spec-012 §6 (`relevance-filter`), one of the four cooperating units
|
|
14
|
+
* spec-012 defines (`dna-loader`, `directive-loader`, `relevance-filter`, `context-builder`) — the
|
|
15
|
+
* other three, and the canonical serialized envelope (§7), belong to
|
|
16
|
+
* task-037-role-task-scoped-context (REQ-STATE-05, the `context-builder`/envelope task). For the
|
|
17
|
+
* document scan itself it wraps, not reimplements, `src/memory/query.ts`'s scan primitives
|
|
18
|
+
* (task-008) — no second directory walk or frontmatter parser — and is placed in `src/core` per
|
|
19
|
+
* spec-012 §1 ("folded into the `core` module"). It is re-exported from `src/core`'s barrel, so
|
|
20
|
+
* `src/core` is the import surface for every consumer.
|
|
21
|
+
*
|
|
22
|
+
* **Exclusion set (`dl-028-archived-states-excluded-from-context`, `ready`).** Candidates are dropped
|
|
23
|
+
* when their `status` is archived — `{deprecated, superseded}`, via `src/memory/state-machine.ts`'s
|
|
24
|
+
* shared {@link isArchivedStatus} (the one predicate the default-search path, REQ-STATE-06, consumes
|
|
25
|
+
* too) — **or** `draft`. That `draft` clause is context-only and deliberately NOT mirrored into
|
|
26
|
+
* `memory search`: spec-012 §6 admits only "stable, decided and still-current content" into an
|
|
27
|
+
* execution context, whereas REQ-STATE-06 scopes default-search exclusion to archived content so a
|
|
28
|
+
* draft under active work stays findable. Two different sets, on purpose — see
|
|
29
|
+
* {@link DRAFT_STATUS}. dl-028 also dropped spec-012 §6's former `rejected` entry, a status
|
|
30
|
+
* `spec-001-memory-yaml-schema` removed from every type's machine.
|
|
31
|
+
*
|
|
32
|
+
* Determinism (REQ-SYS-07): no wall-clock, no randomness, no unordered map/set iteration in any
|
|
33
|
+
* output-affecting path. This function READS the live working tree under `root` — it is not pinned to
|
|
34
|
+
* a git revision, and `root`'s content is therefore part of its input, not a constant. What is
|
|
35
|
+
* guaranteed is *referential transparency over that observed state*: for one unchanged working tree,
|
|
36
|
+
* the selection and its order are a pure function of `(the Memory documents under root, memoryYaml,
|
|
37
|
+
* element, limits)`, so repeated calls return the identical array. See
|
|
38
|
+
* {@link filterRelevantMemoryDocuments}'s own doc comment for the exact ordering/bounding contract.
|
|
39
|
+
*/
|
|
40
|
+
const query_1 = require("../memory/query");
|
|
41
|
+
const state_machine_1 = require("../memory/state-machine");
|
|
42
|
+
/** spec-012 §6's default {@link ContextLimits} (`maxDocs: 40`, `maxBytes: 262144` / 256 KiB). */
|
|
43
|
+
exports.DEFAULT_CONTEXT_LIMITS = { maxDocs: 40, maxBytes: 262144 };
|
|
44
|
+
/** P5.3.3-relevance-filtering.feature's "Edge - no documents pass the relevance threshold" note,
|
|
45
|
+
* verbatim: `'a note "no relevant Memory found for task" is recorded'`. */
|
|
46
|
+
exports.NO_RELEVANT_MEMORY_NOTE = 'no relevant Memory found for task';
|
|
47
|
+
/**
|
|
48
|
+
* spec-012 §6's per-tier score weights, isolated as named constants so the scoring formula
|
|
49
|
+
* (`1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)`) reads as tiers, not magic numbers. Each weight is a
|
|
50
|
+
* strict order of magnitude above the next so any T(n) hit outranks every combination of lower tiers
|
|
51
|
+
* (a single doc can carry at most a handful of T4 overlaps, far below `TIER_3_TRACEABILITY = 10`).
|
|
52
|
+
*/
|
|
53
|
+
const TIER_1_EXPLICIT_LINK = 1000;
|
|
54
|
+
const TIER_2_RELEASE_SCOPE = 100;
|
|
55
|
+
const TIER_3_TRACEABILITY = 10;
|
|
56
|
+
// T4 (keyword/tag overlap) contributes its raw overlap count, weight 1 — spec-012 §6.
|
|
57
|
+
/** Frontmatter fields spec-012 §6 T1 treats as explicit links. Each may carry a single id or a list
|
|
58
|
+
* of them — see {@link asLinkIds} and `dl-045-absorbed-bug-back-reference`. */
|
|
59
|
+
const LINK_FRONTMATTER_FIELDS = ['adr', 'spec', 'dl', 'bug'];
|
|
60
|
+
/**
|
|
61
|
+
* `draft` — the one status excluded from an assembled context *beyond* the shared archived set.
|
|
62
|
+
* spec-012 §6 admits only "stable, decided and still-current content" into an execution context, so a
|
|
63
|
+
* draft — a document whose content is not yet submitted, let alone agreed — is dropped regardless of
|
|
64
|
+
* score. Kept as a named constant rather than folded into `isArchivedStatus` precisely because it is
|
|
65
|
+
* NOT archived: `memory search` must keep returning drafts (REQ-STATE-06 excludes archived content
|
|
66
|
+
* only), and collapsing the two sets would hide in-progress work from the search surface.
|
|
67
|
+
*/
|
|
68
|
+
const DRAFT_STATUS = 'draft';
|
|
69
|
+
/**
|
|
70
|
+
* True when a candidate's `status` bars it from an assembled agent context (spec-012 §6): the shared
|
|
71
|
+
* archived set `{deprecated, superseded}` ({@link isArchivedStatus}, ratified by
|
|
72
|
+
* `dl-028-archived-states-excluded-from-context`) widened by {@link DRAFT_STATUS}.
|
|
73
|
+
* A document with no `status` frontmatter at all is not excluded — absence is not a decision.
|
|
74
|
+
*/
|
|
75
|
+
function isExcludedFromContext(status) {
|
|
76
|
+
return status === DRAFT_STATUS || (0, state_machine_1.isArchivedStatus)(status);
|
|
77
|
+
}
|
|
78
|
+
/** A traceability token: a feature id (`P5.3.3`, `P1.9`, ...) or a SARD requirement id
|
|
79
|
+
* (`REQ-PERF-05`, `REQ-SYS-07`, ...) — spec-012 §6 T3's "shared traceability keys". */
|
|
80
|
+
const TRACEABILITY_PATTERN = /\bP\d+(?:\.\d+)+\b|\bREQ-[A-Z]+-\d+\b/g;
|
|
81
|
+
/** Lowercase alphanumeric word tokens — spec-012 §6 T4's "title tokens". */
|
|
82
|
+
const WORD_PATTERN = /[a-z0-9]+/g;
|
|
83
|
+
function asString(value) {
|
|
84
|
+
return typeof value === 'string' ? value : undefined;
|
|
85
|
+
}
|
|
86
|
+
function asStringArray(value) {
|
|
87
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read a {@link LINK_FRONTMATTER_FIELDS} value as the list of ids it names, accepting BOTH a single
|
|
91
|
+
* string and an array of them.
|
|
92
|
+
*
|
|
93
|
+
* `dl-045-absorbed-bug-back-reference` made the task `bug:` field a list, so a bug absorbed into an
|
|
94
|
+
* existing task's Acceptance Criteria — rather than given its own fix task — keeps the back-reference
|
|
95
|
+
* `bug.sync_state` binds to. Reading these fields with a bare {@link asString} would have made that
|
|
96
|
+
* change **silently lossy**: `asString` returns `undefined` for an array, so an absorbed bug would
|
|
97
|
+
* stop scoring as a T1 explicit link and drop out of its own host task's assembled context, with no
|
|
98
|
+
* error to notice.
|
|
99
|
+
*
|
|
100
|
+
* Both shapes stay valid on purpose. Every fix task written before dl-045 carries a single id, and
|
|
101
|
+
* `adr`/`spec`/`dl` are unchanged by that decision — narrowing to arrays would break them all.
|
|
102
|
+
*/
|
|
103
|
+
function asLinkIds(value) {
|
|
104
|
+
const single = asString(value);
|
|
105
|
+
return single !== undefined ? [single] : asStringArray(value);
|
|
106
|
+
}
|
|
107
|
+
/** T1: the set of ids the element's frontmatter explicitly references (`adr`, `spec`, `dl`, `bug`,
|
|
108
|
+
* `depends_on`) — spec-012 §6. Insertion order follows {@link LINK_FRONTMATTER_FIELDS}'s fixed
|
|
109
|
+
* declaration order, then each field's own array order, then `depends_on`'s (REQ-SYS-07: no
|
|
110
|
+
* unordered iteration). */
|
|
111
|
+
function collectLinkedIds(frontmatter) {
|
|
112
|
+
const ids = new Set();
|
|
113
|
+
for (const field of LINK_FRONTMATTER_FIELDS) {
|
|
114
|
+
for (const value of asLinkIds(frontmatter[field])) {
|
|
115
|
+
if (value.length > 0)
|
|
116
|
+
ids.add(value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const dependency of asStringArray(frontmatter.depends_on))
|
|
120
|
+
ids.add(dependency);
|
|
121
|
+
return ids;
|
|
122
|
+
}
|
|
123
|
+
/** T3: the set of `P*`/`REQ-*` traceability tokens found across every string-valued frontmatter field
|
|
124
|
+
* (`ref`, `tags`, ...) and, when supplied, the document body — spec-012 §6. */
|
|
125
|
+
function collectTraceabilityKeys(frontmatter, body) {
|
|
126
|
+
const keys = new Set();
|
|
127
|
+
const scan = (text) => {
|
|
128
|
+
for (const match of text.matchAll(TRACEABILITY_PATTERN))
|
|
129
|
+
keys.add(match[0]);
|
|
130
|
+
};
|
|
131
|
+
for (const value of Object.values(frontmatter)) {
|
|
132
|
+
if (typeof value === 'string')
|
|
133
|
+
scan(value);
|
|
134
|
+
else if (Array.isArray(value))
|
|
135
|
+
for (const entry of value)
|
|
136
|
+
if (typeof entry === 'string')
|
|
137
|
+
scan(entry);
|
|
138
|
+
}
|
|
139
|
+
if (body !== undefined)
|
|
140
|
+
scan(body);
|
|
141
|
+
return keys;
|
|
142
|
+
}
|
|
143
|
+
/** T4: the element/document's keyword set — its `tags:` entries plus its title's lowercase word
|
|
144
|
+
* tokens — spec-012 §6 ("docs whose tags: or title tokens intersect the element's tags/title
|
|
145
|
+
* tokens"). */
|
|
146
|
+
function collectKeywords(frontmatter) {
|
|
147
|
+
const keywords = new Set();
|
|
148
|
+
for (const tag of asStringArray(frontmatter.tags))
|
|
149
|
+
keywords.add(tag.toLowerCase());
|
|
150
|
+
const title = asString(frontmatter.title);
|
|
151
|
+
if (title !== undefined)
|
|
152
|
+
for (const word of title.toLowerCase().match(WORD_PATTERN) ?? [])
|
|
153
|
+
keywords.add(word);
|
|
154
|
+
return keywords;
|
|
155
|
+
}
|
|
156
|
+
function intersectionSize(a, b) {
|
|
157
|
+
let count = 0;
|
|
158
|
+
for (const value of a)
|
|
159
|
+
if (b.has(value))
|
|
160
|
+
count += 1;
|
|
161
|
+
return count;
|
|
162
|
+
}
|
|
163
|
+
function hasIntersection(a, b) {
|
|
164
|
+
for (const value of a)
|
|
165
|
+
if (b.has(value))
|
|
166
|
+
return true;
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/** T2: is `documentPath` under the element's release scope? True when the element declares a
|
|
170
|
+
* `release` and either the candidate document's own `release:` frontmatter matches it exactly, or its
|
|
171
|
+
* path contains that release as a path segment (covers release-line-scoped docs whose own frontmatter
|
|
172
|
+
* has no `release` field) — spec-012 §6 ("docs under the element's release / release-line path"). */
|
|
173
|
+
function isSameReleaseScope(elementRelease, documentPath, documentFrontmatter) {
|
|
174
|
+
if (elementRelease === undefined)
|
|
175
|
+
return false;
|
|
176
|
+
return documentPath.includes(`/${elementRelease}/`) || asString(documentFrontmatter.release) === elementRelease;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Rank and bound the Memory documents relevant to `element` (spec-012 §6, REQ-PERF-05). Deterministic
|
|
180
|
+
* end to end (REQ-SYS-07):
|
|
181
|
+
*
|
|
182
|
+
* 1. **Scan** every document `memoryYaml` declares under `root` ({@link listMemoryDocumentPaths}'s
|
|
183
|
+
* already-sorted order, task-008) — no second directory walk.
|
|
184
|
+
* 2. **Exclude** the element's own document (never relevant to itself) and any document
|
|
185
|
+
* {@link isExcludedFromContext} bars — `draft` plus the shared archived set `{deprecated,
|
|
186
|
+
* superseded}` (`dl-028`; the archived half is the same predicate REQ-STATE-06 applies to default
|
|
187
|
+
* `memory search`, the `draft` half is context-only).
|
|
188
|
+
* 3. **Score** each remaining document: `1000*T1 + 100*T2 + 10*T3 + overlapCount(T4)` — T1 explicit
|
|
189
|
+
* link, T2 same release scope, T3 shared traceability key, T4 keyword/tag overlap count
|
|
190
|
+
* (spec-012 §6's exact formula). A document scoring `0` (no tier hit at all) is **not relevant**
|
|
191
|
+
* and is dropped — this is the relevance threshold P5.3.3-relevance-filtering.feature's "Edge - no
|
|
192
|
+
* documents pass the relevance threshold" scenario exercises.
|
|
193
|
+
* 4. **Order**: score DESC, then `type` ASC, then `id` ASC (falling back to `path` when a document has
|
|
194
|
+
* no `id`) — a total, deterministic tie-break (spec-012 §6/REQ-SYS-07).
|
|
195
|
+
* 5. **Bound**: walk in that order, including documents until either `limits.maxDocs` or
|
|
196
|
+
* `limits.maxBytes` (summed UTF-8 body bytes) would be exceeded, then **stop** — never skip a
|
|
197
|
+
* lower-ranked document to fit under a cap while a higher-ranked one was excluded, and never
|
|
198
|
+
* partially include a document (spec-012 §6's "deterministic truncation").
|
|
199
|
+
*
|
|
200
|
+
* Returns {@link NO_RELEVANT_MEMORY_NOTE} in `note` only when **step 3 left nothing** — i.e. no
|
|
201
|
+
* document passed the relevance threshold at all (P5.3.3's edge-case scenario, verbatim wording).
|
|
202
|
+
* When documents scored as relevant but step 5's caps admitted none of them, `documents` is empty and
|
|
203
|
+
* `note` is omitted: relevant Memory *was* found, so the note would assert something false.
|
|
204
|
+
*/
|
|
205
|
+
function filterRelevantMemoryDocuments(root, memoryYaml, element, limits = exports.DEFAULT_CONTEXT_LIMITS) {
|
|
206
|
+
const linkedIds = collectLinkedIds(element.frontmatter);
|
|
207
|
+
const elementRelease = asString(element.frontmatter.release);
|
|
208
|
+
const elementTraceability = collectTraceabilityKeys(element.frontmatter);
|
|
209
|
+
const elementKeywords = collectKeywords(element.frontmatter);
|
|
210
|
+
const scored = [];
|
|
211
|
+
for (const path of (0, query_1.listMemoryDocumentPaths)(root, memoryYaml)) {
|
|
212
|
+
const { frontmatter, body } = (0, query_1.loadMemoryDocumentSummary)(root, path);
|
|
213
|
+
const type = asString(frontmatter.type);
|
|
214
|
+
const id = asString(frontmatter.id);
|
|
215
|
+
const status = asString(frontmatter.status);
|
|
216
|
+
if (isExcludedFromContext(status))
|
|
217
|
+
continue;
|
|
218
|
+
if (type === element.type && id === element.id)
|
|
219
|
+
continue;
|
|
220
|
+
const t1 = id !== undefined && linkedIds.has(id);
|
|
221
|
+
const t2 = isSameReleaseScope(elementRelease, path, frontmatter);
|
|
222
|
+
const t3 = hasIntersection(elementTraceability, collectTraceabilityKeys(frontmatter, body));
|
|
223
|
+
const t4OverlapCount = intersectionSize(elementKeywords, collectKeywords(frontmatter));
|
|
224
|
+
const score = (t1 ? TIER_1_EXPLICIT_LINK : 0) +
|
|
225
|
+
(t2 ? TIER_2_RELEASE_SCOPE : 0) +
|
|
226
|
+
(t3 ? TIER_3_TRACEABILITY : 0) +
|
|
227
|
+
t4OverlapCount;
|
|
228
|
+
if (score <= 0)
|
|
229
|
+
continue;
|
|
230
|
+
scored.push({ path, type, id, title: asString(frontmatter.title), status, frontmatter, body, score });
|
|
231
|
+
}
|
|
232
|
+
scored.sort((a, b) => {
|
|
233
|
+
if (a.score !== b.score)
|
|
234
|
+
return b.score - a.score;
|
|
235
|
+
const typeA = a.type ?? '';
|
|
236
|
+
const typeB = b.type ?? '';
|
|
237
|
+
if (typeA !== typeB)
|
|
238
|
+
return typeA < typeB ? -1 : 1;
|
|
239
|
+
const idA = a.id ?? a.path;
|
|
240
|
+
const idB = b.id ?? b.path;
|
|
241
|
+
return idA < idB ? -1 : idA > idB ? 1 : 0;
|
|
242
|
+
});
|
|
243
|
+
const documents = [];
|
|
244
|
+
let totalBytes = 0;
|
|
245
|
+
for (const doc of scored) {
|
|
246
|
+
if (documents.length >= limits.maxDocs)
|
|
247
|
+
break;
|
|
248
|
+
const docBytes = Buffer.byteLength(doc.body, 'utf-8');
|
|
249
|
+
if (totalBytes + docBytes > limits.maxBytes)
|
|
250
|
+
break;
|
|
251
|
+
documents.push(doc);
|
|
252
|
+
totalBytes += docBytes;
|
|
253
|
+
}
|
|
254
|
+
// The note is keyed to `scored`, NOT to `documents`: an empty `documents` with a non-empty `scored`
|
|
255
|
+
// means relevant Memory existed and was bounded out, which the note must not claim away.
|
|
256
|
+
return scored.length === 0 ? { documents, note: exports.NO_RELEVANT_MEMORY_NOTE } : { documents };
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=relevance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relevance.js","sourceRoot":"","sources":["../../src/core/relevance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return the `--reason` value out of a `CoreFn`'s parsed `options` (the `ParamsContext.options`
|
|
3
|
+
* value-bearing seam, `../core/registry.ts` — the same one `memoryAddFn` reads `--type`/`--title`/
|
|
4
|
+
* `--tags` from), normalized to the form the commit trailer records; or throw a `UsageError` (exit 2)
|
|
5
|
+
* when it is absent (`options` itself undefined, or present without a `reason` key) or cannot be
|
|
6
|
+
* recorded. Call this BEFORE any registry/document lookup in a mutating `CoreFn` so a refused
|
|
7
|
+
* `--reason` makes no change.
|
|
8
|
+
*
|
|
9
|
+
* Returning the NORMALIZED text rather than the raw argument is what keeps the commit trailer and
|
|
10
|
+
* `memory reject`'s `rejection_reason` frontmatter field (`spec-010` — the one verb that writes the
|
|
11
|
+
* reason twice) carrying byte-identical text: the rule is applied once, here, not at each sink.
|
|
12
|
+
*/
|
|
13
|
+
export declare function requireReason(options: Readonly<Record<string, string>> | undefined): string;
|
|
14
|
+
/**
|
|
15
|
+
* The same, for a verb whose `--reason` is OPTIONAL — `memory deprecate` (`dl-027` option (a)):
|
|
16
|
+
* `undefined` when none was given (the verb then exits `0` and commits a subject-only message), the
|
|
17
|
+
* normalized text when one was.
|
|
18
|
+
*
|
|
19
|
+
* A reason that is *declared but empty* is a usage error here too, exactly as on the approval gates
|
|
20
|
+
* (dl-067 S2, ratified). `buildOptionValues` (`src/cli/program.ts`) keeps any `typeof value ===
|
|
21
|
+
* 'string'` precisely so a core op can tell "not given" from "given empty"; honouring that
|
|
22
|
+
* distinction means refusing the empty one, not silently discarding it. This changes a merged verb's
|
|
23
|
+
* behaviour — nothing pinned the old one, and the old one was bug-042 F2 on the verb where it bites
|
|
24
|
+
* hardest, `deprecate` having no `Approver:` line of its own to lose.
|
|
25
|
+
*/
|
|
26
|
+
export declare function optionalReason(options: Readonly<Record<string, string>> | undefined): string | undefined;
|
|
27
|
+
//# sourceMappingURL=require-reason.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-reason.d.ts","sourceRoot":"","sources":["../../src/core/require-reason.ts"],"names":[],"mappings":"AA2DA;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,CAM3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGxG"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireReason = requireReason;
|
|
4
|
+
exports.optionalReason = optionalReason;
|
|
5
|
+
/**
|
|
6
|
+
* `--reason` mandatory-argument pre-flight check (REQ-SEC-04 — task-041-mandatory-reason-on-verbs).
|
|
7
|
+
*
|
|
8
|
+
* Decisions must be explainable in the audit trail: `memory approve` (P1.7) and `memory reject`
|
|
9
|
+
* (P1.8) both record an approver/rejecter identity, an ISO-8601 timestamp, and a reason in the
|
|
10
|
+
* resulting git commit body (P1.7/P1.8 BDD "the git commit records the approver identity, timestamp,
|
|
11
|
+
* and reason"). The reason text can only be recorded if the caller supplied one, so both operations
|
|
12
|
+
* refuse before any read/write when `--reason` is omitted — the argument-validation step every
|
|
13
|
+
* mutating `CoreFn` already performs inline for its own required options (e.g. `memoryAddFn`'s
|
|
14
|
+
* `--type`/`--title` checks, task-020), factored out here once both approve and reject need the
|
|
15
|
+
* identical check (this task is their shared prerequisite, per its own Implementation Notes).
|
|
16
|
+
*
|
|
17
|
+
* This mirrors `requireGitIdentity`'s (`./git-identity.ts`, REQ-SEC-01, task-014) role and calling
|
|
18
|
+
* convention: a mutating `CoreFn` calls it first, before any registry lookup or file access, so an
|
|
19
|
+
* omitted `--reason` makes no change (REQ-SEC-04's fit criterion). Unlike `requireGitIdentity` (an
|
|
20
|
+
* ambient precondition, not tied to a CLI argument, returning a `CoreResult.error` mapped to exit 1)
|
|
21
|
+
* this is an ARGUMENT-shape problem — spec-005-cli-command-contract §1's "missing required argument"
|
|
22
|
+
* case — so, matching `memoryAddFn`'s own `--type`/`--title` checks and `dnaSetFn`'s invalid-key-path
|
|
23
|
+
* check, it `throw`s a `UsageError` (exit **2**, `./exit-code.ts`'s `exitCodeForThrow`) rather than
|
|
24
|
+
* returning a `CoreResult`.
|
|
25
|
+
*
|
|
26
|
+
* `memory deprecate` (P1.9, task-048) deliberately does NOT call {@link requireReason}:
|
|
27
|
+
* `spec-008-cli-grammar.md` §2 marks `--reason` optional "elsewhere (e.g. `memory deprecate`)", and
|
|
28
|
+
* `P1.9-memory-deprecate.feature` has no "missing reason" error scenario (unlike P1.7/P1.8). Since
|
|
29
|
+
* `task-072` it calls {@link optionalReason} instead of reading `options?.reason` raw.
|
|
30
|
+
*
|
|
31
|
+
* `task-072-fix-reason-trailer-contract` (`dl-067-reason-trailer-contract`, fixing `bug-042`) widens
|
|
32
|
+
* both checks from "was a reason given?" to "can this reason be RECORDED?". Those turned out to be
|
|
33
|
+
* different questions: `--reason ""` *was* given, was accepted at exit `0`, and left a bare `Reason:`
|
|
34
|
+
* that made `wingfoil memory history` report an approval gate crossed by nobody, for no reason. The
|
|
35
|
+
* shape rule itself is not decided here — it lives once, in `src/memory/commit-message.ts`, beside the
|
|
36
|
+
* formatter that writes the trailer and consumed by the reader in `src/memory/audit.ts` (dl-067
|
|
37
|
+
* clause 5). This module only maps a defect onto the CLI's usage-error exit code.
|
|
38
|
+
*/
|
|
39
|
+
const commit_message_1 = require("../memory/commit-message");
|
|
40
|
+
const usage_error_1 = require("./usage-error");
|
|
41
|
+
/** Exact refusal message required by REQ-SEC-04's fit criterion (spec-008-cli-grammar §2's ground-truth wording) — do not reword. */
|
|
42
|
+
const MISSING_REASON_ERROR = 'missing required argument: --reason';
|
|
43
|
+
/**
|
|
44
|
+
* The given reason in its declared normal form, or a `UsageError` (exit 2) naming the defect that
|
|
45
|
+
* makes it unrecordable — blank, carrying a reserved trailer line, or ending in one
|
|
46
|
+
* (`reasonDefect`, `src/memory/commit-message.ts`).
|
|
47
|
+
*
|
|
48
|
+
* The message is deliberately NOT {@link MISSING_REASON_ERROR}: that string answers the *omitted*
|
|
49
|
+
* case, and `spec-008-cli-grammar` §2 plus the `P1.7`/`P1.8` BDD features quote it verbatim for that
|
|
50
|
+
* case alone, so reusing it would make those scenarios ambiguous about which failure they pin
|
|
51
|
+
* (dl-067 S1).
|
|
52
|
+
*/
|
|
53
|
+
function recordableReason(reason) {
|
|
54
|
+
const defect = (0, commit_message_1.reasonDefect)(reason);
|
|
55
|
+
if (defect !== null) {
|
|
56
|
+
throw new usage_error_1.UsageError((0, commit_message_1.reasonDefectMessage)(defect));
|
|
57
|
+
}
|
|
58
|
+
return (0, commit_message_1.normalizeReason)(reason);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Return the `--reason` value out of a `CoreFn`'s parsed `options` (the `ParamsContext.options`
|
|
62
|
+
* value-bearing seam, `../core/registry.ts` — the same one `memoryAddFn` reads `--type`/`--title`/
|
|
63
|
+
* `--tags` from), normalized to the form the commit trailer records; or throw a `UsageError` (exit 2)
|
|
64
|
+
* when it is absent (`options` itself undefined, or present without a `reason` key) or cannot be
|
|
65
|
+
* recorded. Call this BEFORE any registry/document lookup in a mutating `CoreFn` so a refused
|
|
66
|
+
* `--reason` makes no change.
|
|
67
|
+
*
|
|
68
|
+
* Returning the NORMALIZED text rather than the raw argument is what keeps the commit trailer and
|
|
69
|
+
* `memory reject`'s `rejection_reason` frontmatter field (`spec-010` — the one verb that writes the
|
|
70
|
+
* reason twice) carrying byte-identical text: the rule is applied once, here, not at each sink.
|
|
71
|
+
*/
|
|
72
|
+
function requireReason(options) {
|
|
73
|
+
const reason = options?.reason;
|
|
74
|
+
if (reason === undefined) {
|
|
75
|
+
throw new usage_error_1.UsageError(MISSING_REASON_ERROR);
|
|
76
|
+
}
|
|
77
|
+
return recordableReason(reason);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The same, for a verb whose `--reason` is OPTIONAL — `memory deprecate` (`dl-027` option (a)):
|
|
81
|
+
* `undefined` when none was given (the verb then exits `0` and commits a subject-only message), the
|
|
82
|
+
* normalized text when one was.
|
|
83
|
+
*
|
|
84
|
+
* A reason that is *declared but empty* is a usage error here too, exactly as on the approval gates
|
|
85
|
+
* (dl-067 S2, ratified). `buildOptionValues` (`src/cli/program.ts`) keeps any `typeof value ===
|
|
86
|
+
* 'string'` precisely so a core op can tell "not given" from "given empty"; honouring that
|
|
87
|
+
* distinction means refusing the empty one, not silently discarding it. This changes a merged verb's
|
|
88
|
+
* behaviour — nothing pinned the old one, and the old one was bug-042 F2 on the verb where it bites
|
|
89
|
+
* hardest, `deprecate` having no `Approver:` line of its own to lose.
|
|
90
|
+
*/
|
|
91
|
+
function optionalReason(options) {
|
|
92
|
+
const reason = options?.reason;
|
|
93
|
+
return reason === undefined ? undefined : recordableReason(reason);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=require-reason.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-reason.js","sourceRoot":"","sources":["../../src/core/require-reason.ts"],"names":[],"mappings":";;AAuEA,sCAMC;AAcD,wCAGC;AA9FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,6DAA8F;AAE9F,+CAA2C;AAE3C,qIAAqI;AACrI,MAAM,oBAAoB,GAAG,qCAAqC,CAAC;AAEnE;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CAAC,MAAc;IACtC,MAAM,MAAM,GAAG,IAAA,6BAAY,EAAC,MAAM,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,wBAAU,CAAC,IAAA,oCAAmB,EAAC,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAA,gCAAe,EAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,aAAa,CAAC,OAAqD;IACjF,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAC/B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,wBAAU,CAAC,oBAAoB,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,cAAc,CAAC,OAAqD;IAClF,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,CAAC;IAC/B,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core result & error shapes (spec-006-core-domain-api §2 — "Function shape").
|
|
3
|
+
*
|
|
4
|
+
* Every core function returns `Promise<CoreResult<T>>`, a discriminated union — never a thrown
|
|
5
|
+
* error for *expected* domain failures (illegal state transition, missing element, schema
|
|
6
|
+
* violation). That is what lets `src/cli`'s exit-code/stderr rendering and `src/mcp`'s Tool
|
|
7
|
+
* `isError`/Resource-read-failure response be driven off the very same value, rather than each
|
|
8
|
+
* surface independently deciding what counts as a failure and how to report it.
|
|
9
|
+
*/
|
|
10
|
+
/** The fixed `CoreError.code` enumeration (spec-006 §2, verbatim). */
|
|
11
|
+
export type CoreErrorCode = 'NOT_FOUND' | 'INVALID_TRANSITION' | 'VALIDATION' | 'CONFLICT' | 'IO';
|
|
12
|
+
/**
|
|
13
|
+
* An expected domain failure carried by a failed {@link CoreResult} (spec-006 §2): a fixed
|
|
14
|
+
* {@link CoreErrorCode}, a human-readable `message`, and optional structured `details`. The single
|
|
15
|
+
* shape both surfaces map to an exit code / MCP error response.
|
|
16
|
+
*/
|
|
17
|
+
export interface CoreError {
|
|
18
|
+
readonly code: CoreErrorCode;
|
|
19
|
+
readonly message: string;
|
|
20
|
+
readonly details?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The discriminated-union return of every {@link CoreFn} (spec-006 §2): `ok: true` with the operation's
|
|
24
|
+
* `value` (and, for a mutation, the `commit` it produced), or `ok: false` with a {@link CoreError}.
|
|
25
|
+
* Expected domain failures travel here, never as a thrown exception.
|
|
26
|
+
*/
|
|
27
|
+
export type CoreResult<T> = {
|
|
28
|
+
readonly ok: true;
|
|
29
|
+
readonly value: T;
|
|
30
|
+
readonly commit?: {
|
|
31
|
+
readonly sha: string;
|
|
32
|
+
readonly message: string;
|
|
33
|
+
};
|
|
34
|
+
} | {
|
|
35
|
+
readonly ok: false;
|
|
36
|
+
readonly error: CoreError;
|
|
37
|
+
};
|
|
38
|
+
/** Build a success {@link CoreResult}, optionally carrying the git commit the operation produced. */
|
|
39
|
+
export declare function coreOk<T>(value: T, commit?: {
|
|
40
|
+
sha: string;
|
|
41
|
+
message: string;
|
|
42
|
+
}): CoreResult<T>;
|
|
43
|
+
/** Build a failure {@link CoreResult} from a {@link CoreError}. */
|
|
44
|
+
export declare function coreErr<T = never>(error: CoreError): CoreResult<T>;
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,oBAAoB,GAAG,YAAY,GAAG,UAAU,GAAG,IAAI,CAAC;AAElG;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IACpB;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AAEtD,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAE5F;AAED,mEAAmE;AACnE,wBAAgB,OAAO,CAAC,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,CAElE"}
|