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,352 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkAssignable = checkAssignable;
|
|
4
|
+
exports.checkUnreferenced = checkUnreferenced;
|
|
5
|
+
exports.updateRoleAssignments = updateRoleAssignments;
|
|
6
|
+
/**
|
|
7
|
+
* Role ↔ directive binding support — the `roles.yaml`-facing half of the Directives pillar's
|
|
8
|
+
* commands. It began as `wingfoil directive assign`'s support module (task-051-directive-assign,
|
|
9
|
+
* P3.2), whose two pieces were built so that `directive remove` (P3.3, task-052) and
|
|
10
|
+
* multi-directive assignment (P3.7, task-056) would not re-derive them; it now holds **three**
|
|
11
|
+
* pieces, because P3.3 turned out to need a different one:
|
|
12
|
+
*
|
|
13
|
+
* **What `directive remove` actually uses.** It uses {@link checkUnreferenced} — which task-052
|
|
14
|
+
* *contributed* here, it did not inherit it — and it uses **neither** of the two task-051 pieces.
|
|
15
|
+
* Not {@link checkAssignable}: a removal has no role argument to validate. And, importantly, **not**
|
|
16
|
+
* {@link updateRoleAssignments}: P3.3 Scenario 2 *refuses* a still-assigned directive rather than
|
|
17
|
+
* unbinding it, so a removal has no `roles.yaml` write to make at all and never reaches the
|
|
18
|
+
* `setRoleAssignmentsInText` → fallback path below. (Correcting task-051's original prediction, which
|
|
19
|
+
* `dl-062-roles-yaml-unwritable-fallback` quotes from this doc range as evidence that P3.3 would be a
|
|
20
|
+
* second consumer of that fallback decision. It is not.)
|
|
21
|
+
*
|
|
22
|
+
* **What P3.7 actually uses — now an observed fact, no longer a prediction.**
|
|
23
|
+
* `task-056-role-based-directive-assignment` shipped multi-directive assignment (P3.7, US-4-06) as a
|
|
24
|
+
* comma-separated `--directive` on the *same* `directive assign` verb — it registers no operation of
|
|
25
|
+
* its own — and it reuses **both** task-051 pieces **without a single change to this file**:
|
|
26
|
+
* {@link checkAssignable} already took a list and validated every id before anything was written
|
|
27
|
+
* (which is exactly P3.7 Sc.3's "no partial assignment is persisted"), and
|
|
28
|
+
* {@link updateRoleAssignments} already took an arbitrary `update` function. So P3.7 *is* the second
|
|
29
|
+
* consumer of the fallback decision that `dl-062-roles-yaml-unwritable-fallback` is about — it reaches
|
|
30
|
+
* the `setRoleAssignmentsInText` → fallback path below on exactly the same inputs `directive assign`
|
|
31
|
+
* always did, no more and no fewer.
|
|
32
|
+
*
|
|
33
|
+
* - {@link checkAssignable} — the pre-write validation: the role must be defined in `dna.yaml`
|
|
34
|
+
* (REQ-SYS-08, via task-034's binding resolver `isRoleDefined`/`UnknownRoleError` — never the
|
|
35
|
+
* approval-authority module, dl-033) and every directive id must exist under
|
|
36
|
+
* `.wingfoil/directives/**` **as committed at `HEAD`** (built-in or custom alike: assignment binds
|
|
37
|
+
* by id and modifies no asset, dl-037/dl-030). Both halves read the committed repository since
|
|
38
|
+
* task-096 (`bug-086`); everything is checked before anything is written, so a request is applied
|
|
39
|
+
* whole or not at all.
|
|
40
|
+
* - {@link checkUnreferenced} — the mirror-image pre-write validation for `directive remove` (P3.3,
|
|
41
|
+
* task-052): REQ-SEC-07 clause (b), refusing to remove a directive any role — or `global` — still
|
|
42
|
+
* binds, naming the referrer.
|
|
43
|
+
* - {@link updateRoleAssignments} — the ONE read → edit → validate → write → commit path for
|
|
44
|
+
* `.wingfoil/roles.yaml`. It edits through the comment-preserving `setRoleAssignmentsInText`
|
|
45
|
+
* (`src/directives/roles-edit.ts`); only when that cannot apply does it consider a whole-file
|
|
46
|
+
* `dump`, and then only for a file with no comment to lose — otherwise it fails closed (bug-019's
|
|
47
|
+
* lesson: never a silent comment loss). **This `#`-gated split is what `dl-062` ratified away**
|
|
48
|
+
* (Q1 option 3, `ready`, approve commit `4cd1876`): `undefined` is to mean `CONFLICT` regardless of
|
|
49
|
+
* any `#`, with the whole-file rewrite reachable only behind an explicit `--force` and a stderr
|
|
50
|
+
* warning naming what it normalizes. That is **not implemented here** — it needs a
|
|
51
|
+
* success-warning channel this codebase does not have (`CoreResult`'s success arm carries `value`
|
|
52
|
+
* and `commit` and nothing else) — and is scheduled to v0.3 as its own task. The code below is
|
|
53
|
+
* therefore knowingly one step behind a ratified decision; read `dl-062`, not this paragraph, for
|
|
54
|
+
* the intended contract.
|
|
55
|
+
*
|
|
56
|
+
* Lives in `src/core` (not `src/directives`) for the same reason `directives-list.ts` does: it
|
|
57
|
+
* operates on `DirectiveFile` (`./loaders`) and returns `CoreResult`s, so a `src/directives` home
|
|
58
|
+
* would need a `core → directives → core` cycle.
|
|
59
|
+
*/
|
|
60
|
+
const path_1 = require("path");
|
|
61
|
+
const js_yaml_1 = require("js-yaml");
|
|
62
|
+
const roles_1 = require("../dna/roles");
|
|
63
|
+
const roles_edit_1 = require("../directives/roles-edit");
|
|
64
|
+
const schema_1 = require("../directives/schema");
|
|
65
|
+
const storage_1 = require("../storage");
|
|
66
|
+
const validation_1 = require("../validation");
|
|
67
|
+
const loaders_1 = require("./loaders");
|
|
68
|
+
const types_1 = require("./types");
|
|
69
|
+
const write_guard_1 = require("./write-guard");
|
|
70
|
+
/**
|
|
71
|
+
* A second sentence for the unknown-role refusal, appended **only** when the *working tree's*
|
|
72
|
+
* `dna.yaml` does define the role — the same diagnostic shape as `task-090`'s `workingTreeWouldGrant`
|
|
73
|
+
* (`./approval-authority.ts`): it tells a user whose editor shows the role why the tool disagrees,
|
|
74
|
+
* instead of leaving them to argue with the file on their screen. Any failure to read or parse the
|
|
75
|
+
* working-tree file is simply "no", so it can never change an outcome owned by `HEAD`.
|
|
76
|
+
*/
|
|
77
|
+
function workingTreeWouldDefine(root, role) {
|
|
78
|
+
try {
|
|
79
|
+
return (0, roles_1.isRoleDefined)((0, loaders_1.loadDnaYaml)(root), role);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validate an assignment request before anything is written. The role is checked first (it is the
|
|
87
|
+
* binding's target and the REQ-SYS-08 check), then each id in argument order; the first failure wins.
|
|
88
|
+
*
|
|
89
|
+
* **The role catalogue is read from the `dna.yaml` committed at `HEAD`**, never from the working tree
|
|
90
|
+
* (task-091, `bug-082-directive-assign-validates-role-against-worktree`,
|
|
91
|
+
* `dl-080-which-baseline-each-command-reads` option (B)). This function therefore takes `root`, not a
|
|
92
|
+
* `DnaYaml`: a caller cannot hand it a working-tree catalogue even by accident, which is what makes
|
|
93
|
+
* the committed baseline a property of the read rather than of a precondition someone must remember
|
|
94
|
+
* to run. An uncommitted role used to be enough to commit a `roles.yaml` binding to a role no commit
|
|
95
|
+
* of the repository defines — a dangling reference by construction, since **REQ-SYS-08** binds
|
|
96
|
+
* directives by role and every other clone sees the binding without the role.
|
|
97
|
+
*
|
|
98
|
+
* The cost was measured and accepted at `dl-080`'s ratification: extending the catalogue becomes
|
|
99
|
+
* *edit, commit, then assign*. `dl-081`/`task-093` are building the `dna` verbs that fold the first
|
|
100
|
+
* two steps into one command; nothing here waits on them.
|
|
101
|
+
*
|
|
102
|
+
* Two fail-closed refusals come with the baseline, mirroring `requireApprovalAuthority`'s: no
|
|
103
|
+
* committed `dna.yaml` at all, and a committed one that does not parse or validate.
|
|
104
|
+
*
|
|
105
|
+
* **The directive inventory is read the same way**, since task-096 /
|
|
106
|
+
* `bug-086-directive-inventory-read-from-the-worktree` — `loadDirectivesAtHead`, the tree at `HEAD`.
|
|
107
|
+
* task-091 left this half on the files on disk and offered a boundary for it (the role catalogue is
|
|
108
|
+
* *governance*, the directive file is the *asset*); its reviewer showed the boundary does not hold,
|
|
109
|
+
* and `bug-086` settles that what actually separated them was **cost**: reading the inventory at a
|
|
110
|
+
* revision needs a directory listing at a revision, which `src/storage` did not have. It has one now
|
|
111
|
+
* (`listPathsAtRev`), so the parameter is gone too and the two halves of this one function read one
|
|
112
|
+
* baseline. Before that, an untracked directive file produced a committed `roles.yaml` binding to a
|
|
113
|
+
* directive present in no commit.
|
|
114
|
+
*
|
|
115
|
+
* An absent or empty committed inventory needs no special case: no id is known, so every request is
|
|
116
|
+
* refused with `unknown directive: <id>`. That is the right answer rather than a tolerated one — "may
|
|
117
|
+
* this id be bound" needs the *positive* fact that a directive with it exists, and a missing record
|
|
118
|
+
* cannot supply one. (Contrast {@link checkUnreferenced}, which needs the *absence* of a fact and is
|
|
119
|
+
* therefore satisfied by a missing record.)
|
|
120
|
+
*
|
|
121
|
+
* @param root - Project root; both committed baselines are read from it.
|
|
122
|
+
* @param role - The role to bind to.
|
|
123
|
+
* @param ids - The directive ids to bind.
|
|
124
|
+
* @returns `undefined` when assignable; otherwise an error carrying P3.2's exact message verbatim as
|
|
125
|
+
* its first sentence — `unknown role '<role>' (not defined in dna.yaml)` or
|
|
126
|
+
* `unknown directive: <id>` — or, for an absent/unreadable committed catalogue or an unreadable
|
|
127
|
+
* committed directive file, a `VALIDATION` refusal naming the baseline.
|
|
128
|
+
*/
|
|
129
|
+
function checkAssignable(root, role, ids) {
|
|
130
|
+
let committed;
|
|
131
|
+
try {
|
|
132
|
+
committed = (0, loaders_1.loadDnaYamlAtHead)(root);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (!(error instanceof validation_1.ValidationError))
|
|
136
|
+
throw error;
|
|
137
|
+
return {
|
|
138
|
+
code: 'VALIDATION',
|
|
139
|
+
message: `cannot resolve the role catalogue: the committed '${loaders_1.DNA_YAML_PATH}' (at HEAD) is not readable as DNA: ` +
|
|
140
|
+
`${error.message}. A role binding is validated against the catalogue the repository records (dl-080), so the ` +
|
|
141
|
+
'committed file must be valid; fix it and commit the fix, then retry.',
|
|
142
|
+
details: { issues: error.issues },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (committed === null) {
|
|
146
|
+
return {
|
|
147
|
+
code: 'VALIDATION',
|
|
148
|
+
message: `cannot resolve the role catalogue: '${loaders_1.DNA_YAML_PATH}' is not committed at HEAD. A binding must reference a ` +
|
|
149
|
+
`role the repository records, not one a working tree holds (REQ-SYS-08, dl-080); commit '${loaders_1.DNA_YAML_PATH}' ` +
|
|
150
|
+
'first, then retry.',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (!(0, roles_1.isRoleDefined)(committed, role)) {
|
|
154
|
+
const uncommittedRole = workingTreeWouldDefine(root, role)
|
|
155
|
+
? ` — the working tree's '${loaders_1.DNA_YAML_PATH}' defines it, but that change is not committed, and a binding is ` +
|
|
156
|
+
`validated against the committed catalogue (REQ-SYS-08, dl-080); commit '${loaders_1.DNA_YAML_PATH}' first, then retry`
|
|
157
|
+
: '';
|
|
158
|
+
return { code: 'NOT_FOUND', message: `${new roles_1.UnknownRoleError(role).message}${uncommittedRole}` };
|
|
159
|
+
}
|
|
160
|
+
let inventory;
|
|
161
|
+
try {
|
|
162
|
+
inventory = (0, loaders_1.loadDirectivesAtHead)(root);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (!(error instanceof validation_1.ValidationError))
|
|
166
|
+
throw error;
|
|
167
|
+
return {
|
|
168
|
+
code: 'VALIDATION',
|
|
169
|
+
message: `cannot resolve the directive inventory: a file committed under '${loaders_1.DIRECTIVES_DIR_PATH}' (at HEAD) is not ` +
|
|
170
|
+
`readable as a directive: ${error.message}. A binding is validated against the directives the repository ` +
|
|
171
|
+
'records (dl-080), so the committed files must be valid; fix it and commit the fix, then retry.',
|
|
172
|
+
details: { issues: error.issues },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const known = new Set(inventory.map((file) => file.frontmatter.id));
|
|
176
|
+
const unknown = ids.find((id) => !known.has(id));
|
|
177
|
+
return unknown === undefined ? undefined : { code: 'NOT_FOUND', message: `unknown directive: ${unknown}` };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* REQ-SEC-07 clause (b) for the Directives surface — *"removal of a still-referenced custom asset is
|
|
181
|
+
* rejected naming the referrer"* — assigned to `task-052-directive-remove` by
|
|
182
|
+
* `dl-030-req-sec-07-referenced-asset-ownership` (`ready`, option (b)). The counterpart to
|
|
183
|
+
* {@link checkAssignable}: that one guards a binding being *made*, this one guards an asset being
|
|
184
|
+
* removed while a binding still names it. It lives here for the same reason, over the same
|
|
185
|
+
* `roles.yaml` shape.
|
|
186
|
+
*
|
|
187
|
+
* `roles.yaml` binds by directive **`frontmatter.id`** (`src/directives/schema.ts`'s `RolesYaml`), so
|
|
188
|
+
* the comparison is against the id, never the filename.
|
|
189
|
+
*
|
|
190
|
+
* Two referrer kinds, checked in this order:
|
|
191
|
+
*
|
|
192
|
+
* 1. A role whose own `assignments` entry names the id → P3.3's verbatim message
|
|
193
|
+
* `cannot remove '<id>': still assigned to role '<role>'`
|
|
194
|
+
* (`p3-directives/P3.3-directive-remove.feature`, "Error - removing a directive still referenced"
|
|
195
|
+
* — do not reword). When several roles bind it the **alphabetically first** is named: the message
|
|
196
|
+
* must be a pure function of the file's content, never of YAML mapping order (REQ-SYS-07), and
|
|
197
|
+
* `directives-list.ts` already walks role names in the same ascending order.
|
|
198
|
+
* 2. `roles.yaml`'s `global` list, which binds the directive to *every* role
|
|
199
|
+
* (`spec-012-context-loader-relevance-filtering` §5) and is therefore the strongest reference of
|
|
200
|
+
* all. `[AUTHORING]`: P3.3 pins only the per-role wording, and a global binding cannot be phrased
|
|
201
|
+
* in it without naming a role the file does not name — so the message is
|
|
202
|
+
* `cannot remove '<id>': still assigned to every role via roles.yaml 'global'`. Recorded in
|
|
203
|
+
* `task-052-directive-remove`'s Execution Notes and raised for ratification, not assumed settled.
|
|
204
|
+
*
|
|
205
|
+
* A directive referenced from a *workflow* step is deliberately not checked: `src/workflow/schema.ts`
|
|
206
|
+
* has no field that can reference a directive, so P3.3's "or workflow step" precondition is vacuous
|
|
207
|
+
* today, and P4.9's workflow half of clause (b) has no owner in v0.2 per `dl-030`.
|
|
208
|
+
*
|
|
209
|
+
* **The bindings are read from the `roles.yaml` committed at `HEAD`**, never from the working tree
|
|
210
|
+
* (task-096, `bug-086-directive-inventory-read-from-the-worktree`, `dl-080` option (B)). This
|
|
211
|
+
* function therefore takes `root`, not a `RolesYaml`: a caller cannot hand it a working-tree copy
|
|
212
|
+
* even by accident, which is what makes the committed baseline a property of the read. Before that,
|
|
213
|
+
* deleting the two `- <id>` lines **without committing** was enough to walk past this check entirely
|
|
214
|
+
* and **destroy** a file the committed `roles.yaml` still bound — on the only verb in the system that
|
|
215
|
+
* deletes an artefact, and against the one clause REQ-SEC-07 (b) exists to enforce.
|
|
216
|
+
*
|
|
217
|
+
* A `roles.yaml` that is **not committed at all** is still "no bindings yet" (task-051/task-053's
|
|
218
|
+
* reading) and permits the removal. That is not a fail-open tolerance: this check needs the *negative*
|
|
219
|
+
* fact *nothing names this id*, and a record that does not exist answers it. (Contrast
|
|
220
|
+
* {@link checkAssignable}, which needs a positive fact and therefore refuses when its record is
|
|
221
|
+
* missing.) A committed `roles.yaml` that does not **validate** is the genuinely unanswerable case,
|
|
222
|
+
* and it refuses.
|
|
223
|
+
*
|
|
224
|
+
* @param root - Project root; the committed `roles.yaml` is read from it (`loadRolesYamlAtHead`).
|
|
225
|
+
* @param id - The directive id about to be removed.
|
|
226
|
+
* @returns `undefined` when nothing references it; otherwise a `CONFLICT` error (exit 1) carrying the
|
|
227
|
+
* message above. `CONFLICT` and not `VALIDATION`: the request is well-formed and the *state*
|
|
228
|
+
* refuses it — the same distinction `directiveCreate` draws for `directive already exists: <name>`,
|
|
229
|
+
* as against `requireCustomAsset`'s `VALIDATION` for a request inadmissible on its own terms. An
|
|
230
|
+
* unreadable committed `roles.yaml` is `VALIDATION`, naming the baseline.
|
|
231
|
+
*/
|
|
232
|
+
function checkUnreferenced(root, id) {
|
|
233
|
+
let rolesYaml;
|
|
234
|
+
try {
|
|
235
|
+
rolesYaml = (0, loaders_1.loadRolesYamlAtHead)(root);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (!(error instanceof validation_1.ValidationError))
|
|
239
|
+
throw error;
|
|
240
|
+
return {
|
|
241
|
+
code: 'VALIDATION',
|
|
242
|
+
message: `cannot resolve the directive bindings: the committed '${loaders_1.ROLES_YAML_PATH}' (at HEAD) is not readable as ` +
|
|
243
|
+
`role assignments: ${error.message}. Whether an asset may be deleted is answered from the bindings the ` +
|
|
244
|
+
'repository records (REQ-SEC-07, dl-080), so the committed file must be valid; fix it and commit the fix, ' +
|
|
245
|
+
'then retry.',
|
|
246
|
+
details: { issues: error.issues },
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (rolesYaml === null)
|
|
250
|
+
return undefined;
|
|
251
|
+
// `Object.entries` (own enumerable properties only) + an explicit ascending sort, never YAML
|
|
252
|
+
// mapping order — REQ-SYS-07, and the same walk `directives-list.ts` uses.
|
|
253
|
+
const [boundRole] = Object.entries(rolesYaml.assignments)
|
|
254
|
+
.filter(([, ids]) => ids.includes(id))
|
|
255
|
+
.map(([role]) => role)
|
|
256
|
+
.sort((a, b) => (a < b ? -1 : 1));
|
|
257
|
+
if (boundRole !== undefined) {
|
|
258
|
+
return { code: 'CONFLICT', message: `cannot remove '${id}': still assigned to role '${boundRole}'` };
|
|
259
|
+
}
|
|
260
|
+
if (rolesYaml.global.includes(id)) {
|
|
261
|
+
return { code: 'CONFLICT', message: `cannot remove '${id}': still assigned to every role via roles.yaml 'global'` };
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
function validationError(error) {
|
|
266
|
+
return (0, types_1.coreErr)({ code: 'VALIDATION', message: error.message, details: { issues: error.issues } });
|
|
267
|
+
}
|
|
268
|
+
/** Parse and schema-check `roles.yaml` text, as a plain object (for re-serialization) plus its typed view. */
|
|
269
|
+
function parseRoles(text, filePath) {
|
|
270
|
+
let raw;
|
|
271
|
+
try {
|
|
272
|
+
raw = (0, validation_1.parseYaml)(text, filePath);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
// `parseYaml` wraps every YAML syntax failure in a `ValidationError` and throws nothing else
|
|
276
|
+
// (`src/validation/yaml.ts`).
|
|
277
|
+
return validationError(error);
|
|
278
|
+
}
|
|
279
|
+
const parsed = schema_1.RolesYaml.safeParse(raw);
|
|
280
|
+
if (!parsed.success)
|
|
281
|
+
return validationError((0, validation_1.toValidationError)(parsed.error, filePath));
|
|
282
|
+
return (0, types_1.coreOk)({ raw: raw, roles: parsed.data });
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Apply `update` to `assignments.<role>` in `.wingfoil/roles.yaml` and commit the result as ONE commit
|
|
286
|
+
* staging only that file.
|
|
287
|
+
*
|
|
288
|
+
* - A missing `roles.yaml` means "no bindings yet" (task-053's reading) and is created with a
|
|
289
|
+
* deterministic `dump` — there is nothing to preserve.
|
|
290
|
+
* - An unchanged list is an idempotent success: nothing is written and no commit is made.
|
|
291
|
+
* - The edit goes through `setRoleAssignmentsInText`, keeping every comment and unrelated line. When it
|
|
292
|
+
* cannot apply, a whole-file `dump` is used only if the file contains no `#` at all; otherwise the
|
|
293
|
+
* result is `CONFLICT` and the file is left untouched.
|
|
294
|
+
* - No second schema pass on the output is needed: the in-place edit is only accepted when its
|
|
295
|
+
* re-parse equals the validated input with `assignments.<role>` replaced by a string list
|
|
296
|
+
* (`setRoleAssignmentsInText`'s self-check), and the whole-file `dump` serializes exactly that object.
|
|
297
|
+
*
|
|
298
|
+
* Commits through `commitPaths`, which since bug-027's fix (`git commit --only -- <paths>`) records
|
|
299
|
+
* exactly the path it is given and leaves anything else staged untouched.
|
|
300
|
+
*
|
|
301
|
+
* @param root - Project root.
|
|
302
|
+
* @param role - The role whose list is updated (already validated by the caller).
|
|
303
|
+
* @param update - Pure function from the current list to the desired list.
|
|
304
|
+
* @param message - The commit subject.
|
|
305
|
+
* @returns The role's resulting list, with the commit when one was made.
|
|
306
|
+
* @throws whatever `git` raises inside `commitPaths`.
|
|
307
|
+
*/
|
|
308
|
+
function updateRoleAssignments(root, role, update, message) {
|
|
309
|
+
// dl-080 (B) / bug-078: refuse while `roles.yaml` carries modifications this operation does not
|
|
310
|
+
// own — otherwise an unrelated uncommitted edit rides into `wf(directive): assign <id> to <role>`,
|
|
311
|
+
// whose subject names only the binding. Before the read, so the refusal cannot depend on a value
|
|
312
|
+
// the dirty copy contributed. An ABSENT `roles.yaml` is clean (porcelain reports nothing), which is
|
|
313
|
+
// what keeps the first `directive assign` on a fresh project working.
|
|
314
|
+
const unmodified = (0, write_guard_1.requireUnmodifiedTarget)(root, loaders_1.ROLES_YAML_PATH);
|
|
315
|
+
if (!unmodified.ok)
|
|
316
|
+
return unmodified;
|
|
317
|
+
const filePath = (0, path_1.join)(root, loaders_1.ROLES_YAML_PATH);
|
|
318
|
+
const exists = (0, storage_1.documentExists)(filePath);
|
|
319
|
+
const text = exists ? (0, storage_1.readDocument)(filePath) : '';
|
|
320
|
+
let raw = { version: 1, assignments: {}, global: [] };
|
|
321
|
+
let current = [];
|
|
322
|
+
if (exists) {
|
|
323
|
+
const parsed = parseRoles(text, filePath);
|
|
324
|
+
if (!parsed.ok)
|
|
325
|
+
return parsed;
|
|
326
|
+
raw = parsed.value.raw;
|
|
327
|
+
current = Object.prototype.hasOwnProperty.call(parsed.value.roles.assignments, role)
|
|
328
|
+
? parsed.value.roles.assignments[role]
|
|
329
|
+
: [];
|
|
330
|
+
}
|
|
331
|
+
const next = update(current);
|
|
332
|
+
if (JSON.stringify(next) === JSON.stringify(current))
|
|
333
|
+
return (0, types_1.coreOk)({ assignments: current });
|
|
334
|
+
const wholeFile = () => (0, js_yaml_1.dump)({ ...raw, assignments: { ...raw.assignments, [role]: next } }, { lineWidth: -1 });
|
|
335
|
+
let serialized = exists ? (0, roles_edit_1.setRoleAssignmentsInText)(text, role, next) : wholeFile();
|
|
336
|
+
if (serialized === undefined) {
|
|
337
|
+
if (text.includes('#')) {
|
|
338
|
+
return (0, types_1.coreErr)({
|
|
339
|
+
code: 'CONFLICT',
|
|
340
|
+
message: `roles.yaml cannot be updated without discarding its comments; edit assignments.${role} by hand`,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
serialized = wholeFile();
|
|
344
|
+
}
|
|
345
|
+
(0, storage_1.writeDocument)(filePath, serialized);
|
|
346
|
+
const sha = (0, storage_1.commitPaths)(root, [loaders_1.ROLES_YAML_PATH], message);
|
|
347
|
+
const leaked = (0, write_guard_1.committedScopeError)(root, sha, loaders_1.ROLES_YAML_PATH, serialized);
|
|
348
|
+
if (leaked)
|
|
349
|
+
return leaked;
|
|
350
|
+
return (0, types_1.coreOk)({ assignments: next }, { sha, message });
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=directive-assign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directive-assign.js","sourceRoot":"","sources":["../../src/core/directive-assign.ts"],"names":[],"mappings":";;AAgJA,0CAkDC;AAuDD,8CA8BC;AA6CD,sDAkDC;AAtXD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,+BAA4B;AAE5B,qCAA+B;AAE/B,wCAA+D;AAE/D,yDAAoE;AACpE,iDAAiD;AACjD,wCAAsF;AACtF,8CAA8E;AAE9E,uCASmB;AAEnB,mCAA0C;AAC1C,+CAA6E;AAQ7E;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,IAAY,EAAE,IAAY;IACxD,IAAI,CAAC;QACH,OAAO,IAAA,qBAAa,EAAC,IAAA,qBAAW,EAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,SAAgB,eAAe,CAAC,IAAY,EAAE,IAAY,EAAE,GAAsB;IAChF,IAAI,SAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,SAAS,GAAG,IAAA,2BAAiB,EAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,4BAAe,CAAC;YAAE,MAAM,KAAK,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,OAAO,EACL,qDAAqD,uBAAa,sCAAsC;gBACxG,GAAG,KAAK,CAAC,OAAO,8FAA8F;gBAC9G,sEAAsE;YACxE,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;SAClC,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,OAAO,EACL,uCAAuC,uBAAa,yDAAyD;gBAC7G,2FAA2F,uBAAa,IAAI;gBAC5G,oBAAoB;SACvB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,IAAA,qBAAa,EAAC,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,eAAe,GAAG,sBAAsB,CAAC,IAAI,EAAE,IAAI,CAAC;YACxD,CAAC,CAAC,0BAA0B,uBAAa,mEAAmE;gBAC1G,2EAA2E,uBAAa,qBAAqB;YAC/G,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,IAAI,wBAAgB,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,eAAe,EAAE,EAAE,CAAC;IACnG,CAAC;IAED,IAAI,SAAmC,CAAC;IACxC,IAAI,CAAC;QACH,SAAS,GAAG,IAAA,8BAAoB,EAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,4BAAe,CAAC;YAAE,MAAM,KAAK,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,OAAO,EACL,mEAAmE,6BAAmB,qBAAqB;gBAC3G,4BAA4B,KAAK,CAAC,OAAO,iEAAiE;gBAC1G,gGAAgG;YAClG,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;SAClC,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IACpE,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,sBAAsB,OAAO,EAAE,EAAE,CAAC;AAC7G,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,SAAgB,iBAAiB,CAAC,IAAY,EAAE,EAAU;IACxD,IAAI,SAA2B,CAAC;IAChC,IAAI,CAAC;QACH,SAAS,GAAG,IAAA,6BAAmB,EAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,4BAAe,CAAC;YAAE,MAAM,KAAK,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,OAAO,EACL,yDAAyD,yBAAe,iCAAiC;gBACzG,qBAAqB,KAAK,CAAC,OAAO,sEAAsE;gBACxG,2GAA2G;gBAC3G,aAAa;YACf,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;SAClC,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACzC,6FAA6F;IAC7F,2EAA2E;IAC3E,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC;SACtD,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;SACrC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC;SACrB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,8BAA8B,SAAS,GAAG,EAAE,CAAC;IACvG,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,yDAAyD,EAAE,CAAC;IACtH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,eAAe,CAAC,KAAsB;IAC7C,OAAO,IAAA,eAAO,EAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACpG,CAAC;AAED,8GAA8G;AAC9G,SAAS,UAAU,CAAC,IAAY,EAAE,QAAgB;IAChD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAA,sBAAS,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,6FAA6F;QAC7F,8BAA8B;QAC9B,OAAO,eAAe,CAAC,KAAwB,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,MAAM,GAAG,kBAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,eAAe,CAAC,IAAA,8BAAiB,EAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvF,OAAO,IAAA,cAAM,EAAC,EAAE,GAAG,EAAE,GAA8B,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,qBAAqB,CACnC,IAAY,EACZ,IAAY,EACZ,MAAyD,EACzD,OAAe;IAEf,gGAAgG;IAChG,mGAAmG;IACnG,iGAAiG;IACjG,oGAAoG;IACpG,sEAAsE;IACtE,MAAM,UAAU,GAAG,IAAA,qCAAuB,EAAC,IAAI,EAAE,yBAAe,CAAC,CAAC;IAClE,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC;IAEtC,MAAM,QAAQ,GAAG,IAAA,WAAI,EAAC,IAAI,EAAE,yBAAe,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAA,wBAAc,EAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAA,sBAAY,EAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAElD,IAAI,GAAG,GAA4B,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC/E,IAAI,OAAO,GAAsB,EAAE,CAAC;IACpC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAC9B,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;QACvB,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC;YAClF,CAAC,CAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAc;YACpD,CAAC,CAAC,EAAE,CAAC;IACT,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;QAAE,OAAO,IAAA,cAAM,EAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IAE9F,MAAM,SAAS,GAAG,GAAW,EAAE,CAC7B,IAAA,cAAI,EAAC,EAAE,GAAG,GAAG,EAAE,WAAW,EAAE,EAAE,GAAI,GAAG,CAAC,WAAuC,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IACtH,IAAI,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,IAAA,qCAAwB,EAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IACnF,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,IAAA,eAAO,EAAC;gBACb,IAAI,EAAE,UAAU;gBAChB,OAAO,EAAE,kFAAkF,IAAI,UAAU;aAC1G,CAAC,CAAC;QACL,CAAC;QACD,UAAU,GAAG,SAAS,EAAE,CAAC;IAC3B,CAAC;IAED,IAAA,uBAAa,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,IAAA,qBAAW,EAAC,IAAI,EAAE,CAAC,yBAAe,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,IAAA,iCAAmB,EAAC,IAAI,EAAE,GAAG,EAAE,yBAAe,EAAE,UAAU,CAAC,CAAC;IAC3E,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,OAAO,IAAA,cAAM,EAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { DirectiveFrontmatter, RolesYaml } from '../directives/schema';
|
|
2
|
+
import { type DirectiveFile } from './loaders';
|
|
3
|
+
/**
|
|
4
|
+
* {@link DirectiveListEntry.assignment} for a directive no role names and `roles.yaml`'s `global`
|
|
5
|
+
* list does not carry. The exact wording is the BDD's own: P3.4's "each directive shows its assigned
|
|
6
|
+
* roles (or `unassigned`)".
|
|
7
|
+
*/
|
|
8
|
+
export declare const UNASSIGNED_ASSIGNMENT = "unassigned";
|
|
9
|
+
/**
|
|
10
|
+
* {@link DirectiveListEntry.assignment} prefix for a directive in `roles.yaml`'s `global` list.
|
|
11
|
+
* Phrased from `spec-012-context-loader-relevance-filtering` §5's own description of that list — the
|
|
12
|
+
* "global directives applied to all roles" — because such a directive is bound to *every* role and so
|
|
13
|
+
* is neither enumerable as a role list nor, in any sense, `unassigned`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const GLOBAL_ASSIGNMENT = "global (all roles)";
|
|
16
|
+
/** One directive in the listing: the file, and who is bound to it. */
|
|
17
|
+
export interface DirectiveListEntry {
|
|
18
|
+
/** Root-relative path as `loadDirectives` records it (`directives/<built-in|custom>/<name>.md`). */
|
|
19
|
+
readonly path: string;
|
|
20
|
+
/** The file's validated frontmatter, unchanged — carries `id`, `name`, `kind`, `title`, `tags`, `ref`. */
|
|
21
|
+
readonly frontmatter: DirectiveFrontmatter;
|
|
22
|
+
/**
|
|
23
|
+
* Role names whose `roles.yaml` `assignments` entry names this directive's **`frontmatter.id`**
|
|
24
|
+
* (never its `name` — `assignments` values are ids, per `src/directives/schema.ts`'s `RolesYaml`),
|
|
25
|
+
* ascending. Empty for a directive nothing binds *and* for a `global` one, whose binding is carried
|
|
26
|
+
* by {@link global} instead — listing every role name for a global directive would require reading
|
|
27
|
+
* `dna.yaml`'s role catalogue, a different pillar (REQ-SYS-02), and would still misreport a role
|
|
28
|
+
* that has no `assignments` entry of its own.
|
|
29
|
+
*/
|
|
30
|
+
readonly roles: readonly string[];
|
|
31
|
+
/** `true` when `roles.yaml`'s `global` list names this directive's id — it applies to every role. */
|
|
32
|
+
readonly global: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The two fields above rendered as one human-readable string, so P3.4's `unassigned` wording is
|
|
35
|
+
* present in the payload itself and reads identically under `--format console`, `json` and `yaml`.
|
|
36
|
+
* (`--format console` renders the payload as pretty-printed JSON today; `renderSuccess`,
|
|
37
|
+
* `src/cli/output.ts`, states that a command-specific console rendering needs a command-specific
|
|
38
|
+
* spec, and none exists for this command.) Never display-only: `roles`/`global` carry the same
|
|
39
|
+
* fact structurally for machine consumers.
|
|
40
|
+
*/
|
|
41
|
+
readonly assignment: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The `directives list` payload (`dl-042-directives-list-output-contract`): the inventory and the
|
|
45
|
+
* operator diagnostics about it, side by side. `[AUTHORING]` like {@link DirectiveListEntry} (dl-042 C).
|
|
46
|
+
*/
|
|
47
|
+
export interface DirectiveListing {
|
|
48
|
+
/** One entry per directive file on disk — never deduplicated (see the module doc comment). */
|
|
49
|
+
readonly entries: readonly DirectiveListEntry[];
|
|
50
|
+
/** Shadowed ids (always); no-assignments and dangling-binding warnings (with a role filter). Fixed
|
|
51
|
+
* order, empty when there is nothing to report. */
|
|
52
|
+
readonly warnings: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Annotate `directiveFiles` with their `roles.yaml` bindings, optionally keeping only those bound to
|
|
56
|
+
* `role`, and attach the warnings described on {@link DirectiveListing}.
|
|
57
|
+
*
|
|
58
|
+
* `rolesYaml` is optional: `undefined` means the project has no `roles.yaml`, which is "nothing is
|
|
59
|
+
* bound yet" (every entry `unassigned`), not a failure — the Directives files and `roles.yaml` are
|
|
60
|
+
* independent artefacts (REQ-SYS-02) and directives can exist before anyone binds them.
|
|
61
|
+
*
|
|
62
|
+
* The `role` filter keeps an entry when that role's own `assignments` name it **or** when it is
|
|
63
|
+
* `global`, because `spec-012` §5 makes globals unconditional for every role. An unbound or unknown
|
|
64
|
+
* role is therefore not an error and not empty: it lists exactly the globals, matching
|
|
65
|
+
* `dl-029-role-with-no-directive-assignments` (the ratified hybrid) rather than inventing a
|
|
66
|
+
* `NOT_FOUND` this read-only command has no grounds to return.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildDirectiveListing(directiveFiles: readonly DirectiveFile[], rolesYaml: RolesYaml | undefined, role?: string): DirectiveListing;
|
|
69
|
+
/**
|
|
70
|
+
* Load both pillars' artefacts and build the listing — the filesystem half of
|
|
71
|
+
* {@link buildDirectiveListing}, kept separate so the annotation rules stay unit-testable without
|
|
72
|
+
* touching disk.
|
|
73
|
+
*
|
|
74
|
+
* `.wingfoil/roles.yaml` is read only when it exists; when it does exist it goes through the same
|
|
75
|
+
* validating `loadRolesYaml` every other consumer uses, so a malformed or schema-invalid file still
|
|
76
|
+
* raises a `ValidationError` (surfaced as `VALIDATION`, exit 1) rather than being silently downgraded
|
|
77
|
+
* to "nothing is bound". Only a genuinely absent file is tolerated.
|
|
78
|
+
*/
|
|
79
|
+
export declare function loadDirectiveListing(root: string, role?: string): DirectiveListing;
|
|
80
|
+
//# sourceMappingURL=directives-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directives-list.d.ts","sourceRoot":"","sources":["../../src/core/directives-list.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAG5E,OAAO,EAAiC,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,oGAAoG;IACpG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0GAA0G;IAC1G,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,qGAAqG;IACrG,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAChD;uDACmD;IACnD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAsCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,cAAc,EAAE,SAAS,aAAa,EAAE,EACxC,SAAS,EAAE,SAAS,GAAG,SAAS,EAChC,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,CAsBlB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAIlF"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GLOBAL_ASSIGNMENT = exports.UNASSIGNED_ASSIGNMENT = void 0;
|
|
4
|
+
exports.buildDirectiveListing = buildDirectiveListing;
|
|
5
|
+
exports.loadDirectiveListing = loadDirectiveListing;
|
|
6
|
+
/**
|
|
7
|
+
* `wingfoil directives list` — the listing behind the `directives.directivesList` operation
|
|
8
|
+
* (P3.4 / US-4-04, BDD `p3-directives/P3.4-directives-list.feature`; spec-006-core-domain-api §3
|
|
9
|
+
* pins the operation's name and both surfaces). task-053-directives-list.
|
|
10
|
+
*
|
|
11
|
+
* **What this is.** An *inventory* of the directive files installed under `.wingfoil/directives/**`,
|
|
12
|
+
* each annotated with the roles that name it in `.wingfoil/roles.yaml`. It is deliberately NOT the
|
|
13
|
+
* per-role *resolved* set that {@link resolveRoleDirectives} (`./context.ts`) produces for an agent
|
|
14
|
+
* execution context; the two answer different questions and are kept separate:
|
|
15
|
+
*
|
|
16
|
+
* - `resolveRoleDirectives` answers "which rules must role R obey", so it deduplicates by directive
|
|
17
|
+
* id and returns one file per id (`spec-012-context-loader-relevance-filtering` §5).
|
|
18
|
+
* - This function answers "which directive files exist here, and who is bound to them" (P3.4: "so I
|
|
19
|
+
* manage rules with visibility"), so it **never deduplicates**. When a built-in and a custom file
|
|
20
|
+
* share an id, both appear, each with its own `path`. `spec-012` §5, as amended by
|
|
21
|
+
* `dl-037-builtin-vs-custom-directive-precedence` (option B.1), requires that a shadowed directive
|
|
22
|
+
* be "reported, never silently dropped"; a listing that hid one would reproduce exactly the defect
|
|
23
|
+
* dl-037 was raised about, in the one command whose purpose is directive visibility.
|
|
24
|
+
*
|
|
25
|
+
* **Warnings** (`dl-042-directives-list-output-contract` A + D, task-055-auto-load-directives-by-role).
|
|
26
|
+
* The payload is {@link DirectiveListing} — `entries` plus a `warnings` channel — so the two things a
|
|
27
|
+
* reader cannot see in the inventory are said out loud: which of two same-id files is in force, and
|
|
28
|
+
* whether a role is bound at all. Neither is computed here. Without a role filter the warnings are
|
|
29
|
+
* {@link selectDirectivesById}'s shadow warnings over every file; with `--role` they are exactly
|
|
30
|
+
* {@link resolveRoleDirectives}' warnings for that role (no-assignments, dangling binding, shadowed
|
|
31
|
+
* ids). Both live in `./context.ts`, so the precedence rule the listing reports is the one context
|
|
32
|
+
* assembly applies — it is never implemented twice.
|
|
33
|
+
*
|
|
34
|
+
* **Placement.** This lives in `src/core` rather than `src/directives` for the same reason
|
|
35
|
+
* `resolveRoleDirectives` does: the {@link DirectiveFile} shape it operates on is declared in
|
|
36
|
+
* `./loaders.ts`, so putting it under `src/directives` would create a `core → directives → core`
|
|
37
|
+
* import cycle. `src/directives` keeps owning the pillar's own schemas (REQ-SYS-02).
|
|
38
|
+
*
|
|
39
|
+
* **Determinism** (REQ-SYS-07): the output is a pure function of the two files on disk. Entry order
|
|
40
|
+
* is `loadDirectives`'s own lexicographic path order; the per-directive `roles` array is built by
|
|
41
|
+
* walking `assignments`' role names in sorted order, never in YAML mapping order.
|
|
42
|
+
*/
|
|
43
|
+
const path_1 = require("path");
|
|
44
|
+
const storage_1 = require("../storage");
|
|
45
|
+
const context_1 = require("./context");
|
|
46
|
+
const loaders_1 = require("./loaders");
|
|
47
|
+
/**
|
|
48
|
+
* {@link DirectiveListEntry.assignment} for a directive no role names and `roles.yaml`'s `global`
|
|
49
|
+
* list does not carry. The exact wording is the BDD's own: P3.4's "each directive shows its assigned
|
|
50
|
+
* roles (or `unassigned`)".
|
|
51
|
+
*/
|
|
52
|
+
exports.UNASSIGNED_ASSIGNMENT = 'unassigned';
|
|
53
|
+
/**
|
|
54
|
+
* {@link DirectiveListEntry.assignment} prefix for a directive in `roles.yaml`'s `global` list.
|
|
55
|
+
* Phrased from `spec-012-context-loader-relevance-filtering` §5's own description of that list — the
|
|
56
|
+
* "global directives applied to all roles" — because such a directive is bound to *every* role and so
|
|
57
|
+
* is neither enumerable as a role list nor, in any sense, `unassigned`.
|
|
58
|
+
*/
|
|
59
|
+
exports.GLOBAL_ASSIGNMENT = 'global (all roles)';
|
|
60
|
+
/** `roles.yaml` as seen when the file is absent: nothing bound, no globals. */
|
|
61
|
+
const NO_BINDINGS = { assignments: {}, global: [] };
|
|
62
|
+
/**
|
|
63
|
+
* Invert `roles.yaml`'s `assignments` (role → directive ids) into directive id → role names.
|
|
64
|
+
*
|
|
65
|
+
* A `Map` rather than a plain object on purpose: the keys are directive ids read from files on disk,
|
|
66
|
+
* so an id like `constructor` or `__proto__` would otherwise collide with an `Object.prototype`
|
|
67
|
+
* member — the same hazard `ownAssignments` guards against in `./context.ts`, arriving here from the
|
|
68
|
+
* opposite direction. Role names are walked in sorted order (REQ-SYS-07), which is also what makes
|
|
69
|
+
* each entry's `roles` array ascending without a second sort.
|
|
70
|
+
*/
|
|
71
|
+
function rolesByDirectiveId(rolesYaml) {
|
|
72
|
+
const byId = new Map();
|
|
73
|
+
if (rolesYaml === undefined)
|
|
74
|
+
return byId;
|
|
75
|
+
// `Object.entries` (own enumerable properties only) rather than `Object.keys` + an index read: it
|
|
76
|
+
// hands back the id array already typed, with no `?? []` fallback that `noUncheckedIndexedAccess`
|
|
77
|
+
// would otherwise force onto a key that by construction exists.
|
|
78
|
+
const assignments = Object.entries(rolesYaml.assignments).sort(([a], [b]) => (a < b ? -1 : 1));
|
|
79
|
+
for (const [role, ids] of assignments) {
|
|
80
|
+
for (const id of ids) {
|
|
81
|
+
const roles = byId.get(id);
|
|
82
|
+
if (roles === undefined)
|
|
83
|
+
byId.set(id, [role]);
|
|
84
|
+
// A role listing the same directive id twice must not produce a duplicated role name.
|
|
85
|
+
else if (!roles.includes(role))
|
|
86
|
+
roles.push(role);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return byId;
|
|
90
|
+
}
|
|
91
|
+
/** Render {@link DirectiveListEntry.assignment} from the entry's `roles`/`global` pair. */
|
|
92
|
+
function renderAssignment(roles, isGlobal) {
|
|
93
|
+
const parts = isGlobal ? [exports.GLOBAL_ASSIGNMENT, ...roles] : [...roles];
|
|
94
|
+
return parts.length > 0 ? parts.join(', ') : exports.UNASSIGNED_ASSIGNMENT;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Annotate `directiveFiles` with their `roles.yaml` bindings, optionally keeping only those bound to
|
|
98
|
+
* `role`, and attach the warnings described on {@link DirectiveListing}.
|
|
99
|
+
*
|
|
100
|
+
* `rolesYaml` is optional: `undefined` means the project has no `roles.yaml`, which is "nothing is
|
|
101
|
+
* bound yet" (every entry `unassigned`), not a failure — the Directives files and `roles.yaml` are
|
|
102
|
+
* independent artefacts (REQ-SYS-02) and directives can exist before anyone binds them.
|
|
103
|
+
*
|
|
104
|
+
* The `role` filter keeps an entry when that role's own `assignments` name it **or** when it is
|
|
105
|
+
* `global`, because `spec-012` §5 makes globals unconditional for every role. An unbound or unknown
|
|
106
|
+
* role is therefore not an error and not empty: it lists exactly the globals, matching
|
|
107
|
+
* `dl-029-role-with-no-directive-assignments` (the ratified hybrid) rather than inventing a
|
|
108
|
+
* `NOT_FOUND` this read-only command has no grounds to return.
|
|
109
|
+
*/
|
|
110
|
+
function buildDirectiveListing(directiveFiles, rolesYaml, role) {
|
|
111
|
+
const byId = rolesByDirectiveId(rolesYaml);
|
|
112
|
+
const globalIds = new Set(rolesYaml?.global ?? []);
|
|
113
|
+
const entries = [];
|
|
114
|
+
for (const file of directiveFiles) {
|
|
115
|
+
const roles = byId.get(file.frontmatter.id) ?? [];
|
|
116
|
+
const isGlobal = globalIds.has(file.frontmatter.id);
|
|
117
|
+
if (role !== undefined && !isGlobal && !roles.includes(role))
|
|
118
|
+
continue;
|
|
119
|
+
entries.push({
|
|
120
|
+
path: file.path,
|
|
121
|
+
frontmatter: file.frontmatter,
|
|
122
|
+
// Copy, so two entries sharing an id (a shadowed built-in/custom pair) never alias one array.
|
|
123
|
+
roles: [...roles],
|
|
124
|
+
global: isGlobal,
|
|
125
|
+
assignment: renderAssignment(roles, isGlobal),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const warnings = role === undefined
|
|
129
|
+
? (0, context_1.selectDirectivesById)(directiveFiles).warnings
|
|
130
|
+
: (0, context_1.resolveRoleDirectives)(directiveFiles, rolesYaml ?? NO_BINDINGS, role).warnings;
|
|
131
|
+
return { entries, warnings };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Load both pillars' artefacts and build the listing — the filesystem half of
|
|
135
|
+
* {@link buildDirectiveListing}, kept separate so the annotation rules stay unit-testable without
|
|
136
|
+
* touching disk.
|
|
137
|
+
*
|
|
138
|
+
* `.wingfoil/roles.yaml` is read only when it exists; when it does exist it goes through the same
|
|
139
|
+
* validating `loadRolesYaml` every other consumer uses, so a malformed or schema-invalid file still
|
|
140
|
+
* raises a `ValidationError` (surfaced as `VALIDATION`, exit 1) rather than being silently downgraded
|
|
141
|
+
* to "nothing is bound". Only a genuinely absent file is tolerated.
|
|
142
|
+
*/
|
|
143
|
+
function loadDirectiveListing(root, role) {
|
|
144
|
+
const directiveFiles = (0, loaders_1.loadDirectives)(root);
|
|
145
|
+
const rolesYaml = (0, storage_1.documentExists)((0, path_1.join)(root, '.wingfoil', 'roles.yaml')) ? (0, loaders_1.loadRolesYaml)(root) : undefined;
|
|
146
|
+
return buildDirectiveListing(directiveFiles, rolesYaml, role);
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=directives-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directives-list.js","sourceRoot":"","sources":["../../src/core/directives-list.ts"],"names":[],"mappings":"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"}
|