wingfoil 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/init-command.d.ts.map +1 -1
- package/dist/cli/init-command.js +2 -1
- package/dist/cli/init-command.js.map +1 -1
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +117 -29
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/registrar.d.ts +15 -6
- package/dist/cli/registrar.d.ts.map +1 -1
- package/dist/cli/registrar.js +4 -0
- package/dist/cli/registrar.js.map +1 -1
- package/dist/core/exit-code.d.ts.map +1 -1
- package/dist/core/exit-code.js +7 -6
- package/dist/core/exit-code.js.map +1 -1
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +232 -89
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +5 -3
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +5 -3
- package/dist/core/init.js.map +1 -1
- package/dist/core/registry.d.ts +69 -17
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js.map +1 -1
- package/dist/directives/schema.d.ts +3 -3
- package/dist/directives/schema.js +3 -3
- package/dist/mcp/index.d.ts +1 -1
- package/dist/memory/add.d.ts +62 -5
- package/dist/memory/add.d.ts.map +1 -1
- package/dist/memory/add.js +130 -7
- package/dist/memory/add.js.map +1 -1
- package/dist/memory/audit.d.ts +26 -6
- package/dist/memory/audit.d.ts.map +1 -1
- package/dist/memory/audit.js +27 -11
- package/dist/memory/audit.js.map +1 -1
- package/dist/memory/commit-message.js +1 -1
- package/dist/memory/commit-message.js.map +1 -1
- package/dist/memory/history.d.ts +1 -1
- package/dist/memory/history.js +1 -1
- package/dist/memory/index.d.ts +3 -3
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +6 -2
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/state-machine.js +2 -2
- package/dist/storage/templates.d.ts.map +1 -1
- package/dist/storage/templates.js +12 -1
- package/dist/storage/templates.js.map +1 -1
- package/dist/validation/id.d.ts +26 -0
- package/dist/validation/id.d.ts.map +1 -1
- package/dist/validation/id.js +64 -1
- package/dist/validation/id.js.map +1 -1
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +4 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/secret-scan.d.ts +7 -5
- package/dist/validation/secret-scan.d.ts.map +1 -1
- package/dist/validation/secret-scan.js +7 -5
- package/dist/validation/secret-scan.js.map +1 -1
- package/dist/workflow/schema.d.ts.map +1 -1
- package/dist/workflow/schema.js +27 -1
- package/dist/workflow/schema.js.map +1 -1
- package/package.json +23 -9
package/dist/core/index.js
CHANGED
|
@@ -211,13 +211,15 @@ const dnaShowFn = async (params) => {
|
|
|
211
211
|
* task-093-dna-mutation-surface-add-remove-update) — the mutating-op template `dna set` established
|
|
212
212
|
* (P2.1, task-025), now with one structure-aware traversal underneath all four verbs:
|
|
213
213
|
*
|
|
214
|
-
* 1.
|
|
214
|
+
* 1. **Argument validation** — done by the callers before this body runs (`dnaPathPositional`,
|
|
215
|
+
* `dnaMutationRequest`, `dnaSetFn`'s `--value` check): a missing required argument, or a dotted
|
|
216
|
+
* path `splitDnaPath` cannot parse (an empty segment, the BDD's `..language`; an unterminated
|
|
217
|
+
* quote; a `"` no delimiter can account for), is a usage error: `throw new UsageError(...)` → exit
|
|
218
|
+
* **2** (mapped by `exitCodeForThrow`), per spec-005-cli-command-contract §1, whatever the git
|
|
219
|
+
* identity (task-125, `bug-172`). A path that is well-formed but names nothing the schema declares
|
|
220
|
+
* is a different failure and exits **1** — see step 5.
|
|
221
|
+
* 2. **`requireGitIdentity` pre-flight** (REQ-SEC-01, task-014) — refuse before any read/write when
|
|
215
222
|
* `user.name`/`user.email` are unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
216
|
-
* 2. **Argument validation** — a missing required argument, or a dotted path `splitDnaPath` cannot
|
|
217
|
-
* parse (an empty segment, the BDD's `..language`; an unterminated quote; a `"` no delimiter can
|
|
218
|
-
* account for), is a usage error: `throw new UsageError(...)` → exit **2**
|
|
219
|
-
* (mapped by `exitCodeForThrow`), per spec-005-cli-command-contract §1. A path that is well-formed
|
|
220
|
-
* but names nothing the schema declares is a different failure and exits **1** — see step 4.
|
|
221
223
|
* 3. **`requireUnmodifiedTarget` pre-flight** (`dl-080`(B) / `bug-078`, task-092) — refuse, before
|
|
222
224
|
* anything is read or written, while `dna.yaml` carries modifications this operation does not own.
|
|
223
225
|
* The plain "refuse a dirty target" rule applies here unaltered: all four verbs **edit an existing
|
|
@@ -445,23 +447,30 @@ const DNA_VALUE_OPTION = {
|
|
|
445
447
|
const DNA_SET_VALUE_OPTION = {
|
|
446
448
|
name: 'value',
|
|
447
449
|
required: true,
|
|
448
|
-
description: 'the new value for <path> (scalar fields only — use
|
|
450
|
+
description: 'the new value for <path> (scalar fields only — use dna update for collections and lists)',
|
|
449
451
|
};
|
|
450
452
|
const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
|
|
451
453
|
name: (0, path_2.dnaEntryOptionName)(field),
|
|
452
454
|
description: `'${field}' of the entry <path> names, where that collection declares it`,
|
|
453
455
|
}));
|
|
456
|
+
/** A single-valued option of {@link MemoryAddParams.options}: a repeated occurrence keeps the last. */
|
|
457
|
+
function singleOption(options, name) {
|
|
458
|
+
const value = options?.[name];
|
|
459
|
+
return typeof value === 'string' || value === undefined ? value : value[value.length - 1];
|
|
460
|
+
}
|
|
454
461
|
/**
|
|
455
462
|
* `memory add` `CoreOperation.fn` (P1.3, `mutates: true` — the first Memory-document mutation and the
|
|
456
463
|
* pattern for all Memory CRUD; spec-006-core-domain-api §3 memory table). Follows `dna set`'s
|
|
457
464
|
* mutating-op template (task-025) exactly, over the Memory store instead of `dna.yaml`:
|
|
458
465
|
*
|
|
459
|
-
* 1.
|
|
460
|
-
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
461
|
-
* 2. **Argument validation** — a missing `--type`/`--title` is a usage error: `throw new UsageError`
|
|
466
|
+
* 1. **Argument validation** — a missing `--type`/`--title` is a usage error: `throw new UsageError`
|
|
462
467
|
* → exit **2** with the exact `missing required argument: --<name>` message
|
|
463
468
|
* (spec-008-cli-grammar §5, mapped by `exitCodeForThrow`), same classification as `dna set`'s
|
|
464
|
-
* missing positional.
|
|
469
|
+
* missing positional. So is every spelling fault of `--set` (`spec-008` §10: no `=`, a name that
|
|
470
|
+
* is not a field name, a reserved name, a blank value, a name given twice — task-110). It runs
|
|
471
|
+
* first, so a malformed invocation exits 2 whatever the git identity (task-125, `bug-172`).
|
|
472
|
+
* 2. **`requireGitIdentity` pre-flight** (REQ-SEC-01, task-014) — refuse before any read/write when
|
|
473
|
+
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
465
474
|
* 3. **Resolve `--type` against the `memory.yaml` committed at `HEAD`** (spec-001-memory-yaml-schema)
|
|
466
475
|
* via {@link resolveAddType} — the type registry, the type's `path` and its `template` scaffold
|
|
467
476
|
* all resolve against the repository as committed, never against the working tree (task-095,
|
|
@@ -470,13 +479,17 @@ const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
|
|
|
470
479
|
* domain `NOT_FOUND` (exit 1) with the exact P1.3 message
|
|
471
480
|
* `unknown memory type '<t>' (not defined in memory.yaml)`, returned BEFORE any write.
|
|
472
481
|
* 4. **Generate the id** deterministically from the type's committed `id_pattern` (task-002's
|
|
473
|
-
* `generateId`, REQ-SYS-07)
|
|
474
|
-
*
|
|
482
|
+
* `generateId`, REQ-SYS-07), in `spec-001`'s fixed order (task-110, `dl-107` S2): first every
|
|
483
|
+
* frontmatter/context token from `--set` (a `--set` name the committed `id_pattern` and `path` do
|
|
484
|
+
* not contain is refused, exit 1; a token with no value names the option that supplies it), then
|
|
485
|
+
* the `{slug}` from the title, and — only for a `{n}`-token pattern — a sequence counter over the
|
|
486
|
+
* type's directory, matched against the already-materialized pattern (`src/memory/add.ts`; no wall-clock/random). That
|
|
475
487
|
* counter still reads the WORKING TREE: it is `bug-087` (`release: v0.3`), a different read in
|
|
476
488
|
* this verb and deliberately not task-095's. What changed is only that the directory it counts in
|
|
477
489
|
* is now derived from the committed `path` pattern.
|
|
478
490
|
* 5. **Fill the committed scaffold's bytes** with only the `id`/`status: draft`/`--title`/`--tags`
|
|
479
|
-
* skeleton (P1.3; spec-010-memory-frontmatter-schema)
|
|
491
|
+
* skeleton (P1.3; spec-010-memory-frontmatter-schema) plus the `--set` fields (a context token only
|
|
492
|
+
* where the scaffold declares it), then **write + commit** through task-022's
|
|
480
493
|
* confined `writeMemoryEntry` (REQ-SEC-06 refuse-before-write + one scoped commit
|
|
481
494
|
* `wf(<type>): add <id>`); the returned sha rides `CoreResult.commit`.
|
|
482
495
|
*
|
|
@@ -487,16 +500,19 @@ const DNA_ENTRY_OPTIONS = (0, path_2.dnaEntryOptionNames)().map((field) => ({
|
|
|
487
500
|
*/
|
|
488
501
|
const memoryAddFn = async (params) => {
|
|
489
502
|
const { root, options } = params;
|
|
490
|
-
const
|
|
491
|
-
if (!identity.ok)
|
|
492
|
-
return identity;
|
|
493
|
-
const type = options?.type;
|
|
503
|
+
const type = singleOption(options, 'type');
|
|
494
504
|
if (type === undefined)
|
|
495
505
|
throw new usage_error_1.UsageError('missing required argument: --type');
|
|
496
|
-
const title = options
|
|
506
|
+
const title = singleOption(options, 'title');
|
|
497
507
|
if (title === undefined)
|
|
498
508
|
throw new usage_error_1.UsageError('missing required argument: --title');
|
|
499
|
-
const tags = (0, memory_1.parseTags)(options
|
|
509
|
+
const tags = (0, memory_1.parseTags)(singleOption(options, 'tags'));
|
|
510
|
+
const set = (0, memory_1.parseSetOptions)(options?.set);
|
|
511
|
+
if (!set.ok)
|
|
512
|
+
throw new usage_error_1.UsageError(set.message);
|
|
513
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
514
|
+
if (!identity.ok)
|
|
515
|
+
return identity;
|
|
500
516
|
// task-095 / `bug-085`: the registry, the `path` and the scaffold all resolve at HEAD, inside a
|
|
501
517
|
// function that accepts no parsed `MemoryYaml` — so this verb cannot decide any of the three from
|
|
502
518
|
// a working-tree copy, in the same way task-091 made that true of the four transition verbs.
|
|
@@ -504,12 +520,24 @@ const memoryAddFn = async (params) => {
|
|
|
504
520
|
if (!resolved.ok)
|
|
505
521
|
return resolved;
|
|
506
522
|
const { pathPattern, idPattern, scaffold } = resolved.value;
|
|
523
|
+
const unknown = (0, memory_1.unknownSetNames)(set.values, idPattern, pathPattern);
|
|
524
|
+
if (unknown.length > 0) {
|
|
525
|
+
return (0, types_1.coreErr)({
|
|
526
|
+
code: 'VALIDATION',
|
|
527
|
+
message: unknown
|
|
528
|
+
.map((name) => `--set ${name}: memory type '${type}' has no token {${name}} in its id_pattern or path`)
|
|
529
|
+
.join('; '),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
507
532
|
try {
|
|
508
|
-
|
|
509
|
-
|
|
533
|
+
// spec-001's order: field/context tokens → {slug} → {n}, so the counter sees the materialized prefix.
|
|
534
|
+
const materialized = (0, memory_1.expandFieldTokens)(idPattern, set.values);
|
|
535
|
+
const sequence = (0, memory_1.hasNumericToken)(materialized)
|
|
536
|
+
? (0, memory_1.nextSequenceNumber)((0, memory_1.resolveTypeDirectory)(root, pathPattern, set.values), materialized)
|
|
510
537
|
: 0;
|
|
511
|
-
const id = (0, validation_1.generateId)(
|
|
512
|
-
const content = (0, memory_1.renderAddDocument)(scaffold, { id, title, tags });
|
|
538
|
+
const id = (0, validation_1.generateId)(materialized, { slug: (0, memory_1.slugifyTitle)(title), n: sequence });
|
|
539
|
+
const content = (0, memory_1.renderAddDocument)(scaffold, { id, title, tags, fields: (0, memory_1.writtenFields)(scaffold, set.values) });
|
|
540
|
+
const pathValues = { ...set.values, id };
|
|
513
541
|
const message = `wf(${type}): add ${id}`;
|
|
514
542
|
// dl-080 (B) / bug-078, AC3: this verb CREATES, so the rule is absence rather than cleanliness.
|
|
515
543
|
// `nextSequenceNumber` counts the WORKING TREE, so a tree that disagrees with `HEAD` about how
|
|
@@ -519,11 +547,11 @@ const memoryAddFn = async (params) => {
|
|
|
519
547
|
// `writeMemoryEntry`'s return value because the guard must run BEFORE the write; the resolution
|
|
520
548
|
// is pure, so doing it twice is free of side effects and keeps that throwing storage primitive's
|
|
521
549
|
// contract untouched. A confinement escape still throws `StorageError` from this same call.
|
|
522
|
-
const targetPath = (0, path_1.relative)(root, (0, memory_path_1.resolveConfinedMemoryPath)(root, pathPattern,
|
|
550
|
+
const targetPath = (0, path_1.relative)(root, (0, memory_path_1.resolveConfinedMemoryPath)(root, pathPattern, pathValues)).split(path_1.sep).join('/');
|
|
523
551
|
const absent = (0, write_guard_1.requireAbsentTarget)(root, targetPath);
|
|
524
552
|
if (!absent.ok)
|
|
525
553
|
return absent;
|
|
526
|
-
const { path, sha } = (0, memory_1.writeMemoryEntry)(root, pathPattern,
|
|
554
|
+
const { path, sha } = (0, memory_1.writeMemoryEntry)(root, pathPattern, pathValues, content, message);
|
|
527
555
|
const leaked = (0, write_guard_1.committedScopeError)(root, sha, targetPath, content);
|
|
528
556
|
if (leaked)
|
|
529
557
|
return leaked;
|
|
@@ -662,8 +690,9 @@ const memoryHistoryFn = async (params) => {
|
|
|
662
690
|
* "the content is written — move it forward": it commits the document as the author left it, with
|
|
663
691
|
* `status` advanced. Order, every refusal before the single write:
|
|
664
692
|
*
|
|
665
|
-
* 1.
|
|
666
|
-
*
|
|
693
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit 2), as `memory history` — before the identity
|
|
694
|
+
* check, so the exit code does not depend on the machine (task-125, `bug-172`).
|
|
695
|
+
* 2. **`requireGitIdentity`** (REQ-SEC-01) — exit 1.
|
|
667
696
|
* 3. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` committed at `HEAD` itself
|
|
668
697
|
* (task-091, `dl-080` (B)); no committed machine, an unreadable one, not found, unknown type,
|
|
669
698
|
* invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
|
|
@@ -679,12 +708,12 @@ const memoryHistoryFn = async (params) => {
|
|
|
679
708
|
*/
|
|
680
709
|
const memorySubmitFn = async (params) => {
|
|
681
710
|
const { root, positional: id } = params;
|
|
682
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
683
|
-
if (!identity.ok)
|
|
684
|
-
return identity;
|
|
685
711
|
if (id === undefined || id.trim().length === 0) {
|
|
686
712
|
throw new usage_error_1.UsageError('missing required argument: memory submit <id>');
|
|
687
713
|
}
|
|
714
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
715
|
+
if (!identity.ok)
|
|
716
|
+
return identity;
|
|
688
717
|
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'submit');
|
|
689
718
|
if (!prepared.ok)
|
|
690
719
|
return prepared;
|
|
@@ -711,11 +740,12 @@ const memorySubmitFn = async (params) => {
|
|
|
711
740
|
* (P1.2/P1.10), never written into the message. Order, every refusal before the single write, so
|
|
712
741
|
* "the state is unchanged" (P1.7 sc.2/sc.3) holds by construction:
|
|
713
742
|
*
|
|
714
|
-
* 1.
|
|
743
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit 2), as `memory submit`/`memory history`.
|
|
744
|
+
* 2. **`requireReason`** (REQ-SEC-04, task-041) → `UsageError` `missing required argument: --reason`
|
|
745
|
+
* (exit 2, P1.7 sc.2). Placed before any file is read, so an omitted reason touches nothing. Both
|
|
746
|
+
* usage checks precede the identity check (task-125, `bug-172`).
|
|
747
|
+
* 3. **`requireGitIdentity`** (REQ-SEC-01) — exit 1. It is also the identity the `Approver:` line and
|
|
715
748
|
* the commit's own author record, so the two can never name different principals.
|
|
716
|
-
* 2. **`<id>`** absent or blank → `UsageError` (exit 2), as `memory submit`/`memory history`.
|
|
717
|
-
* 3. **`requireReason`** (REQ-SEC-04, task-041) → `UsageError` `missing required argument: --reason`
|
|
718
|
-
* (exit 2, P1.7 sc.2). Placed before any file is read, so an omitted reason touches nothing.
|
|
719
749
|
* 4. **{@link prepareMemoryTransition}** — it resolves the `memory.yaml` committed at `HEAD` itself
|
|
720
750
|
* (task-091, `dl-080` (B)); no committed machine, an unreadable one, not found, unknown type,
|
|
721
751
|
* invalid state, or an illegal transition, each a `CoreResult.error` (exit 1); an illegal one
|
|
@@ -742,13 +772,13 @@ const memorySubmitFn = async (params) => {
|
|
|
742
772
|
*/
|
|
743
773
|
const memoryApproveFn = async (params) => {
|
|
744
774
|
const { root, positional: id, options } = params;
|
|
745
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
746
|
-
if (!identity.ok)
|
|
747
|
-
return identity;
|
|
748
775
|
if (id === undefined || id.trim().length === 0) {
|
|
749
776
|
throw new usage_error_1.UsageError('missing required argument: memory approve <id>');
|
|
750
777
|
}
|
|
751
778
|
const reason = (0, require_reason_1.requireReason)(options);
|
|
779
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
780
|
+
if (!identity.ok)
|
|
781
|
+
return identity;
|
|
752
782
|
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'approve');
|
|
753
783
|
if (!prepared.ok)
|
|
754
784
|
return prepared;
|
|
@@ -777,10 +807,11 @@ const memoryApproveFn = async (params) => {
|
|
|
777
807
|
* `Approver:`/`Reason:` body (`dl-054-submit-commit-subject-bracket`, CLAUDE.md §5.1). Order, every
|
|
778
808
|
* refusal before the single write:
|
|
779
809
|
*
|
|
780
|
-
* 1.
|
|
781
|
-
* 2.
|
|
782
|
-
*
|
|
783
|
-
*
|
|
810
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`.
|
|
811
|
+
* 2. **`requireReason`** (REQ-SEC-04, task-041-mandatory-reason-on-verbs) → `UsageError`
|
|
812
|
+
* `missing required argument: --reason` (exit `2`, P1.8 sc.3). Both usage checks precede the
|
|
813
|
+
* identity check (task-125, `bug-172`).
|
|
814
|
+
* 3. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`.
|
|
784
815
|
* 4. **{@link prepareMemoryTransition}** with op `reject`, against the `memory.yaml` committed at
|
|
785
816
|
* `HEAD` (task-091, `dl-080` (B)) — no committed machine, an unreadable one, not found,
|
|
786
817
|
* unknown type, invalid state, or an illegal transition (the document is in no `gates`
|
|
@@ -806,13 +837,13 @@ const memoryApproveFn = async (params) => {
|
|
|
806
837
|
*/
|
|
807
838
|
const memoryRejectFn = async (params) => {
|
|
808
839
|
const { root, positional: id, options } = params;
|
|
809
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
810
|
-
if (!identity.ok)
|
|
811
|
-
return identity;
|
|
812
840
|
if (id === undefined || id.trim().length === 0) {
|
|
813
841
|
throw new usage_error_1.UsageError('missing required argument: memory reject <id>');
|
|
814
842
|
}
|
|
815
843
|
const reason = (0, require_reason_1.requireReason)(options);
|
|
844
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
845
|
+
if (!identity.ok)
|
|
846
|
+
return identity;
|
|
816
847
|
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'reject');
|
|
817
848
|
if (!prepared.ok)
|
|
818
849
|
return prepared;
|
|
@@ -849,8 +880,10 @@ const memoryRejectFn = async (params) => {
|
|
|
849
880
|
*
|
|
850
881
|
* Order, every refusal before the single write:
|
|
851
882
|
*
|
|
852
|
-
* 1.
|
|
853
|
-
*
|
|
883
|
+
* 1. **`<id>`** absent or blank → `UsageError` (exit `2`), as `memory submit`/`memory reject`; so is a
|
|
884
|
+
* `--reason` that is given but blank or trailer-shaped (`optionalReason`). Both precede the
|
|
885
|
+
* identity check (task-125, `bug-172`).
|
|
886
|
+
* 2. **`requireGitIdentity`** (REQ-SEC-01) — exit `1`.
|
|
854
887
|
* 3. **{@link prepareMemoryTransition}** with op `deprecate`, against the `memory.yaml` committed at
|
|
855
888
|
* `HEAD` (task-091, `dl-080` (B)) — no committed machine, an unreadable one, not found,
|
|
856
889
|
* unknown type, or a `status` that is not a state of the type, each a
|
|
@@ -876,9 +909,6 @@ const memoryRejectFn = async (params) => {
|
|
|
876
909
|
*/
|
|
877
910
|
const memoryDeprecateFn = async (params) => {
|
|
878
911
|
const { root, positional: id, options } = params;
|
|
879
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
880
|
-
if (!identity.ok)
|
|
881
|
-
return identity;
|
|
882
912
|
if (id === undefined || id.trim().length === 0) {
|
|
883
913
|
throw new usage_error_1.UsageError('missing required argument: memory deprecate <id>');
|
|
884
914
|
}
|
|
@@ -886,6 +916,9 @@ const memoryDeprecateFn = async (params) => {
|
|
|
886
916
|
// trailer — blank or trailer-shaped is a usage error at exit 2, before anything is read or written
|
|
887
917
|
// (`dl-067` clauses 1 and 4; `bug-042` F2/F3, whose amendment makes THIS verb the exploitable one).
|
|
888
918
|
const reason = (0, require_reason_1.optionalReason)(options);
|
|
919
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
920
|
+
if (!identity.ok)
|
|
921
|
+
return identity;
|
|
889
922
|
const prepared = (0, memory_transition_1.prepareMemoryTransition)(root, id, 'deprecate');
|
|
890
923
|
if (!prepared.ok)
|
|
891
924
|
return prepared;
|
|
@@ -905,14 +938,16 @@ const memoryDeprecateFn = async (params) => {
|
|
|
905
938
|
* and the pillar's first CLI verb; spec-006-core-domain-api §3 directives table). Follows `dna set`'s
|
|
906
939
|
* mutating-op template (task-025) exactly, over the Directives pillar instead of `dna.yaml`:
|
|
907
940
|
*
|
|
908
|
-
* 1.
|
|
909
|
-
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
910
|
-
* 2. **Argument validation** — both failures are usage errors (`throw new UsageError` → exit **2**,
|
|
941
|
+
* 1. **Argument validation** — both failures are usage errors (`throw new UsageError` → exit **2**,
|
|
911
942
|
* mapped by `exitCodeForThrow`): an absent `--name` uses spec-008-cli-grammar §4's exact
|
|
912
943
|
* `missing required argument: --<name>` wording (same as `memoryAdd`), and a non-kebab-case name
|
|
913
944
|
* uses P3.1's exact `invalid directive name (use kebab-case)` message
|
|
914
945
|
* ({@link INVALID_DIRECTIVE_NAME_MESSAGE}). Because {@link isValidDirectiveName} runs before any
|
|
915
946
|
* path is built, a traversal-shaped name (`../x`, `a/b`, `/etc/passwd`) can never reach the write.
|
|
947
|
+
* It runs before the identity check, so the exit code does not depend on the machine (task-125,
|
|
948
|
+
* `bug-172`).
|
|
949
|
+
* 2. **`requireGitIdentity` pre-flight** (REQ-SEC-01, task-014) — refuse before any read/write when
|
|
950
|
+
* the git identity is unset, returning its `CoreResult.error` unchanged (exit 1).
|
|
916
951
|
* 3. **Already-exists check** — a file at `.wingfoil/directives/custom/<name>.md` makes this a domain
|
|
917
952
|
* `CONFLICT` (the input is well-formed; the target is taken), returned NOT thrown, with P3.1's exact
|
|
918
953
|
* `directive already exists: <name>` message → exit 1 via `exitCodeForError`. Nothing is written, so
|
|
@@ -928,14 +963,14 @@ const memoryDeprecateFn = async (params) => {
|
|
|
928
963
|
*/
|
|
929
964
|
const directiveCreateFn = async (params) => {
|
|
930
965
|
const { root, options } = params;
|
|
931
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
932
|
-
if (!identity.ok)
|
|
933
|
-
return identity;
|
|
934
966
|
const name = options?.name;
|
|
935
967
|
if (name === undefined)
|
|
936
968
|
throw new usage_error_1.UsageError('missing required argument: --name');
|
|
937
969
|
if (!(0, create_1.isValidDirectiveName)(name))
|
|
938
970
|
throw new usage_error_1.UsageError(create_1.INVALID_DIRECTIVE_NAME_MESSAGE);
|
|
971
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
972
|
+
if (!identity.ok)
|
|
973
|
+
return identity;
|
|
939
974
|
// One spelling of the location, reused for the existence check, the write and the commit scope, so
|
|
940
975
|
// the three can never drift apart. `join` normalizes the separators, so the POSIX form is also the
|
|
941
976
|
// correct absolute path on Windows; the root-relative form is what `commitPaths` stages.
|
|
@@ -966,11 +1001,11 @@ const directiveCreateFn = async (params) => {
|
|
|
966
1001
|
* it, and `spec-008` §1 / `X_cli-cmds.md` list no fourth directive verb — so it is this operation
|
|
967
1002
|
* with a list-valued `--directive`. Same mutating-op order as {@link directiveCreateFn}:
|
|
968
1003
|
*
|
|
969
|
-
* 1. `
|
|
970
|
-
* 2. `--directive` then `--role` presence — `UsageError`, spec-008 §4 wording, exit 2. A `--directive`
|
|
1004
|
+
* 1. `--directive` then `--role` presence — `UsageError`, spec-008 §4 wording, exit 2. A `--directive`
|
|
971
1005
|
* value that parses to **no ids** (`""`, `" "`, `","`) counts as absent, the same reading
|
|
972
1006
|
* `parseTags` takes of an empty `--tags` (`[AUTHORING]`, task-056 D3: before P3.7 such a value
|
|
973
1007
|
* reached step 3 and failed with an empty id in the message, `unknown directive: `).
|
|
1008
|
+
* 2. `requireGitIdentity` pre-flight (REQ-SEC-01), after the usage checks (task-125, `bug-172`).
|
|
974
1009
|
* 3. `checkAssignable` — which resolves **both** of its baselines from the repository as committed at
|
|
975
1010
|
* `HEAD` itself, the role catalogue (task-091, `bug-082`) and the directive inventory (task-096,
|
|
976
1011
|
* `bug-086`), per `dl-080` (B) — returns P3.2's exact
|
|
@@ -988,9 +1023,6 @@ const directiveCreateFn = async (params) => {
|
|
|
988
1023
|
*/
|
|
989
1024
|
const directiveAssignFn = async (params) => {
|
|
990
1025
|
const { root, options } = params;
|
|
991
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
992
|
-
if (!identity.ok)
|
|
993
|
-
return identity;
|
|
994
1026
|
const directive = options?.directive;
|
|
995
1027
|
if (directive === undefined)
|
|
996
1028
|
throw new usage_error_1.UsageError('missing required argument: --directive');
|
|
@@ -1000,6 +1032,9 @@ const directiveAssignFn = async (params) => {
|
|
|
1000
1032
|
const role = options?.role;
|
|
1001
1033
|
if (role === undefined)
|
|
1002
1034
|
throw new usage_error_1.UsageError('missing required argument: --role');
|
|
1035
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
1036
|
+
if (!identity.ok)
|
|
1037
|
+
return identity;
|
|
1003
1038
|
const invalid = (0, directive_assign_1.checkAssignable)(root, role, directives);
|
|
1004
1039
|
if (invalid)
|
|
1005
1040
|
return (0, types_1.coreErr)(invalid);
|
|
@@ -1017,9 +1052,9 @@ const directiveAssignFn = async (params) => {
|
|
|
1017
1052
|
* option (b)) assigns clause (b)'s directive half here. Both are enforced, in this order, and both
|
|
1018
1053
|
* strictly **before** anything on disk changes:
|
|
1019
1054
|
*
|
|
1020
|
-
* 1. `
|
|
1021
|
-
* 2. `<name>` presence — a missing or blank positional is a `UsageError` → exit **2**
|
|
1055
|
+
* 1. `<name>` presence — a missing or blank positional is a `UsageError` → exit **2**
|
|
1022
1056
|
* (`missing required argument: directive remove <name>`, the `memory submit <id>` precedent).
|
|
1057
|
+
* 2. `requireGitIdentity` pre-flight (REQ-SEC-01), after the usage check (task-125, `bug-172`).
|
|
1023
1058
|
* 3. **Resolve the name to a real file**, never to a constructed path. `loadDirectives` reads every
|
|
1024
1059
|
* installed directive and {@link selectDirectivesById} picks the winner for that id under
|
|
1025
1060
|
* `dl-037`'s custom-wins precedence (`src/core/context.ts` — the same primitive `directives list`
|
|
@@ -1067,12 +1102,12 @@ const directiveAssignFn = async (params) => {
|
|
|
1067
1102
|
*/
|
|
1068
1103
|
const directiveRemoveFn = async (params) => {
|
|
1069
1104
|
const { root, positional: name } = params;
|
|
1070
|
-
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
1071
|
-
if (!identity.ok)
|
|
1072
|
-
return identity;
|
|
1073
1105
|
if (name === undefined || name.trim().length === 0) {
|
|
1074
1106
|
throw new usage_error_1.UsageError('missing required argument: directive remove <name>');
|
|
1075
1107
|
}
|
|
1108
|
+
const identity = (0, git_identity_1.requireGitIdentity)(root);
|
|
1109
|
+
if (!identity.ok)
|
|
1110
|
+
return identity;
|
|
1076
1111
|
const directiveFiles = loadOrError(() => (0, loaders_1.loadDirectives)(root));
|
|
1077
1112
|
if (!directiveFiles.ok)
|
|
1078
1113
|
return directiveFiles;
|
|
@@ -1158,28 +1193,69 @@ const directivesListFn = async (params) => {
|
|
|
1158
1193
|
exports.CORE_MODULES = [
|
|
1159
1194
|
{
|
|
1160
1195
|
name: 'dna',
|
|
1196
|
+
description: "read and change dna.yaml, the project's structural map",
|
|
1161
1197
|
operations: {
|
|
1162
1198
|
// The DNA mutation surface (task-093, `dl-081-dna-mutation-surface-shape` option (E)): three
|
|
1163
1199
|
// verbs carrying the collection in their argument rather than in the verb name, so the verb
|
|
1164
1200
|
// count stays constant as `spec-002`'s schema grows and spec-006 §3's one-Tool-per-function rule
|
|
1165
1201
|
// costs three Tools (`dna.add`, `dna.remove`, `dna.update`) instead of the dozen a
|
|
1166
1202
|
// per-collection verb set would need. The path itself is the POSITIONAL `<path>` every command
|
|
1167
|
-
// in this CLI uses for its target (`dl-082-cli-parameter-shape`)
|
|
1168
|
-
// `
|
|
1169
|
-
dnaAdd: {
|
|
1203
|
+
// in this CLI uses for its target (`dl-082-cli-parameter-shape`): each verb declares it as its
|
|
1204
|
+
// `positional` (task-120, what `--help` names), which is why no option declares it here.
|
|
1205
|
+
dnaAdd: {
|
|
1206
|
+
name: 'dnaAdd',
|
|
1207
|
+
mutates: true,
|
|
1208
|
+
description: 'add an entry to a collection, or values to a list',
|
|
1209
|
+
positional: { name: 'path', required: true, description: 'the collection (e.g. team.members) or list (e.g. paths.sources) to add to' },
|
|
1210
|
+
options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
|
|
1211
|
+
example: 'wingfoil dna add team.members --value "Ada Lovelace" --entry-email ada@example.com --entry-roles approver',
|
|
1212
|
+
fn: dnaAddFn,
|
|
1213
|
+
},
|
|
1170
1214
|
// `remove` declares no entry-field options: it takes what to drop, never what to write.
|
|
1171
|
-
dnaRemove: {
|
|
1215
|
+
dnaRemove: {
|
|
1216
|
+
name: 'dnaRemove',
|
|
1217
|
+
mutates: true,
|
|
1218
|
+
description: 'remove a collection entry, or values from a list',
|
|
1219
|
+
positional: { name: 'path', required: true, description: 'the entry to remove (<collection>.<name>), or the list to remove the --value values from' },
|
|
1220
|
+
options: [DNA_VALUE_OPTION],
|
|
1221
|
+
example: 'wingfoil dna remove paths.docs --value README.md',
|
|
1222
|
+
fn: dnaRemoveFn,
|
|
1223
|
+
},
|
|
1172
1224
|
// The FIRST `mutates: true` operation in production (spec-006 §3 dna table) — by construction an
|
|
1173
1225
|
// MCP Tool (`dna.set`) + CLI command (`wingfoil dna set`), and the op the REQ-SYS-05 parity test
|
|
1174
1226
|
// now actually guards (task-025-implement-dna-set). Its second positional became `--value` in
|
|
1175
1227
|
// task-093 (`dl-082`) — a breaking change to a shipped command, made before `minor-v0.2` ships.
|
|
1176
|
-
dnaSet: {
|
|
1177
|
-
|
|
1178
|
-
|
|
1228
|
+
dnaSet: {
|
|
1229
|
+
name: 'dnaSet',
|
|
1230
|
+
mutates: true,
|
|
1231
|
+
description: 'set one scalar field',
|
|
1232
|
+
positional: { name: 'path', required: true, description: 'the dotted path of the scalar field, e.g. project.name (double-quote a segment that contains a dot)' },
|
|
1233
|
+
options: [DNA_SET_VALUE_OPTION],
|
|
1234
|
+
example: 'wingfoil dna set project.name --value "My Project"',
|
|
1235
|
+
fn: dnaSetFn,
|
|
1236
|
+
},
|
|
1237
|
+
dnaShow: {
|
|
1238
|
+
name: 'dnaShow',
|
|
1239
|
+
mutates: false,
|
|
1240
|
+
description: 'print dna.yaml, or one top-level section of it',
|
|
1241
|
+
positional: { name: 'section', description: 'a top-level key: project, modules, stacks, team or paths (omit it for the whole file)' },
|
|
1242
|
+
example: 'wingfoil dna show project',
|
|
1243
|
+
fn: dnaShowFn,
|
|
1244
|
+
},
|
|
1245
|
+
dnaUpdate: {
|
|
1246
|
+
name: 'dnaUpdate',
|
|
1247
|
+
mutates: true,
|
|
1248
|
+
description: 'change fields of an existing collection entry',
|
|
1249
|
+
positional: { name: 'path', required: true, description: 'the entry (<collection>.<name>), or one of its fields (<collection>.<name>.<field>) with --value' },
|
|
1250
|
+
options: [DNA_VALUE_OPTION, ...DNA_ENTRY_OPTIONS],
|
|
1251
|
+
example: 'wingfoil dna update modules.api --entry-description "Public HTTP API"',
|
|
1252
|
+
fn: dnaUpdateFn,
|
|
1253
|
+
},
|
|
1179
1254
|
},
|
|
1180
1255
|
},
|
|
1181
1256
|
{
|
|
1182
1257
|
name: 'memory',
|
|
1258
|
+
description: 'create Memory documents, move them through their state machine, and read them back',
|
|
1183
1259
|
operations: {
|
|
1184
1260
|
// The FIRST Memory-document mutation (P1.3, spec-006 §3 memory table) — `mutates: true`, so by
|
|
1185
1261
|
// construction an MCP Tool (`memory.add`) + CLI command (`wingfoil memory add`), and the second
|
|
@@ -1190,18 +1266,36 @@ exports.CORE_MODULES = [
|
|
|
1190
1266
|
memoryAdd: {
|
|
1191
1267
|
name: 'memoryAdd',
|
|
1192
1268
|
mutates: true,
|
|
1269
|
+
description: "create a document in its type's initial state (draft) from the type's template",
|
|
1193
1270
|
options: [
|
|
1194
|
-
{ name: 'type', required: true },
|
|
1195
|
-
{ name: 'title', required: true },
|
|
1196
|
-
{ name: 'tags' },
|
|
1271
|
+
{ name: 'type', required: true, valueName: 'type', description: 'the Memory type, as the committed memory.yaml declares it' },
|
|
1272
|
+
{ name: 'title', required: true, valueName: 'title', description: 'the document title; also the source of the {slug} token' },
|
|
1273
|
+
{ name: 'tags', valueName: 't1,t2', description: 'comma-separated tags, written as the tags list' },
|
|
1274
|
+
// task-110, `spec-008-cli-grammar` §10 (`dl-107` S2): the value of an id_pattern/path token,
|
|
1275
|
+
// written into the frontmatter field of that name. One declared option — not one derived
|
|
1276
|
+
// option per field, whose first member, `--version`, is a global action flag.
|
|
1277
|
+
{
|
|
1278
|
+
name: 'set',
|
|
1279
|
+
repeatable: true,
|
|
1280
|
+
valueName: 'name=value',
|
|
1281
|
+
description: 'fill the id_pattern/path token {name} and write the frontmatter field of that name (repeatable)',
|
|
1282
|
+
},
|
|
1197
1283
|
],
|
|
1284
|
+
example: 'wingfoil memory add --type task --title "My first task" --tags demo,quickstart',
|
|
1198
1285
|
fn: memoryAddFn,
|
|
1199
1286
|
},
|
|
1200
1287
|
// P1.10 (task-049-memory-history) — `mutates: false`, so by construction an MCP Resource +
|
|
1201
1288
|
// CLI command (`wingfoil memory history <id>`). It declares no flags and no value options: the
|
|
1202
1289
|
// document id rides the generic bare `positional` seam (task-026), per spec-008-cli-grammar §7's
|
|
1203
1290
|
// bare-`<id>` rule for a command whose noun already scopes the type.
|
|
1204
|
-
memoryHistory: {
|
|
1291
|
+
memoryHistory: {
|
|
1292
|
+
name: 'memoryHistory',
|
|
1293
|
+
mutates: false,
|
|
1294
|
+
description: "print a document's audit trail, reconstructed from git",
|
|
1295
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
1296
|
+
example: 'wingfoil memory history task-001-my-first-task',
|
|
1297
|
+
fn: memoryHistoryFn,
|
|
1298
|
+
},
|
|
1205
1299
|
// The FIRST Memory-document READ operation (P1.5, spec-006 §3 memory table) — `mutates: false`,
|
|
1206
1300
|
// so by construction an MCP Resource (`wingfoil://memory/search`, the mechanical zero-argument
|
|
1207
1301
|
// form spec-006 §3 pins verbatim) + CLI command (`wingfoil memory search`). Its keyword rides the
|
|
@@ -1211,12 +1305,26 @@ exports.CORE_MODULES = [
|
|
|
1211
1305
|
memorySearch: {
|
|
1212
1306
|
name: 'memorySearch',
|
|
1213
1307
|
mutates: false,
|
|
1214
|
-
|
|
1308
|
+
description: 'find documents by keyword and/or metadata',
|
|
1309
|
+
positional: { name: 'keyword', description: 'a case-insensitive substring matched against title, id, tags and body (omit it to filter by metadata alone)' },
|
|
1310
|
+
options: [
|
|
1311
|
+
{ name: 'tag', valueName: 'tag', description: 'keep documents carrying this tag' },
|
|
1312
|
+
{ name: 'status', valueName: 'status', description: 'keep documents in this state' },
|
|
1313
|
+
{ name: 'type', valueName: 'type', description: 'keep documents of this Memory type' },
|
|
1314
|
+
],
|
|
1315
|
+
example: 'wingfoil memory search --status approved --type task',
|
|
1215
1316
|
fn: memorySearchFn,
|
|
1216
1317
|
},
|
|
1217
1318
|
// P1.6 (task-045-memory-submit) — `mutates: true`: CLI `wingfoil memory submit <id>` + MCP Tool
|
|
1218
1319
|
// `memory.submit`. The id rides the bare `positional` seam (spec-008 §7); no flags, no options.
|
|
1219
|
-
memorySubmit: {
|
|
1320
|
+
memorySubmit: {
|
|
1321
|
+
name: 'memorySubmit',
|
|
1322
|
+
mutates: true,
|
|
1323
|
+
description: "move a document one step forward along its type's sequence — for the default machine, draft → pending",
|
|
1324
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
1325
|
+
example: 'wingfoil memory submit task-001-my-first-task',
|
|
1326
|
+
fn: memorySubmitFn,
|
|
1327
|
+
},
|
|
1220
1328
|
// P1.7 (task-046-memory-approve) — `mutates: true`: CLI `wingfoil memory approve <id> --reason
|
|
1221
1329
|
// <text>` + MCP Tool `memory.approve`. The id rides the bare `positional` seam (spec-008 §7);
|
|
1222
1330
|
// `--reason` is the one declared value option, `required` per spec-008 §2 / REQ-SEC-04 (the
|
|
@@ -1224,7 +1332,10 @@ exports.CORE_MODULES = [
|
|
|
1224
1332
|
memoryApprove: {
|
|
1225
1333
|
name: 'memoryApprove',
|
|
1226
1334
|
mutates: true,
|
|
1227
|
-
|
|
1335
|
+
description: 'pass a gate: move a document forward from a gated state (default machine: pending → approved)',
|
|
1336
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
1337
|
+
options: [{ name: 'reason', required: true, valueName: 'text', description: "why you approve it, recorded as the commit's Reason:; needs the approver role" }],
|
|
1338
|
+
example: 'wingfoil memory approve task-001-my-first-task --reason "Scope and acceptance criteria are clear."',
|
|
1228
1339
|
fn: memoryApproveFn,
|
|
1229
1340
|
},
|
|
1230
1341
|
// P1.8 (task-047-memory-reject) — `mutates: true`: CLI `wingfoil memory reject <id> --reason
|
|
@@ -1235,7 +1346,10 @@ exports.CORE_MODULES = [
|
|
|
1235
1346
|
memoryReject: {
|
|
1236
1347
|
name: 'memoryReject',
|
|
1237
1348
|
mutates: true,
|
|
1238
|
-
|
|
1349
|
+
description: "send a gated document back to its gate's reject target (default machine: pending → draft)",
|
|
1350
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
1351
|
+
options: [{ name: 'reason', required: true, valueName: 'text', description: "what must change before resubmitting, recorded as the commit's Reason: and as rejection_reason; needs the approver role" }],
|
|
1352
|
+
example: 'wingfoil memory reject task-001-my-first-task --reason "Add acceptance criteria before resubmitting."',
|
|
1239
1353
|
fn: memoryRejectFn,
|
|
1240
1354
|
},
|
|
1241
1355
|
// P1.9 (task-048-memory-deprecate) — `mutates: true`: CLI `wingfoil memory deprecate <id>
|
|
@@ -1246,18 +1360,24 @@ exports.CORE_MODULES = [
|
|
|
1246
1360
|
memoryDeprecate: {
|
|
1247
1361
|
name: 'memoryDeprecate',
|
|
1248
1362
|
mutates: true,
|
|
1249
|
-
|
|
1363
|
+
description: 'retire a document, from any state, to deprecated',
|
|
1364
|
+
positional: { name: 'id', required: true, description: 'the document id, e.g. task-001-my-first-task' },
|
|
1365
|
+
options: [{ name: 'reason', valueName: 'text', description: "why the document is retired, recorded as the commit's Reason: (not blank when given)" }],
|
|
1366
|
+
example: 'wingfoil memory deprecate dl-001-use-postgresql --reason "Superseded by the hosted-DB decision."',
|
|
1250
1367
|
fn: memoryDeprecateFn,
|
|
1251
1368
|
},
|
|
1252
1369
|
},
|
|
1253
1370
|
},
|
|
1254
1371
|
{
|
|
1255
1372
|
name: 'directives',
|
|
1373
|
+
description: 'list the directives and the roles they apply to',
|
|
1256
1374
|
operations: {
|
|
1257
1375
|
directivesList: {
|
|
1258
1376
|
name: 'directivesList',
|
|
1259
1377
|
mutates: false,
|
|
1260
|
-
|
|
1378
|
+
description: 'list directives with the roles each is assigned to',
|
|
1379
|
+
options: [{ name: 'role', valueName: 'role', description: 'keep only the directives that apply to this role, globals included' }],
|
|
1380
|
+
example: 'wingfoil directives list --role developer',
|
|
1261
1381
|
fn: directivesListFn,
|
|
1262
1382
|
},
|
|
1263
1383
|
},
|
|
@@ -1267,16 +1387,27 @@ exports.CORE_MODULES = [
|
|
|
1267
1387
|
operations: {
|
|
1268
1388
|
// Self-named (operation name === module name) — the flat/no-verb `wingfoil paths [category]`
|
|
1269
1389
|
// form (see `deriveVerb`, `./registry.ts`), not `wingfoil paths paths`. `category` rides the
|
|
1270
|
-
// generic bare positional (task-026's seam)
|
|
1271
|
-
paths: {
|
|
1390
|
+
// generic bare positional (task-026's seam), declared as `positional` so `--help` names it.
|
|
1391
|
+
paths: {
|
|
1392
|
+
name: 'paths',
|
|
1393
|
+
mutates: false,
|
|
1394
|
+
description: 'print the resource paths declared in dna.yaml paths:',
|
|
1395
|
+
positional: { name: 'category', description: 'sources, tests, docs, config or governance (omit it for the whole map)' },
|
|
1396
|
+
flags: [{ name: 'list', description: 'accepted for the planned drill-down view; it does not change the output yet' }],
|
|
1397
|
+
example: 'wingfoil paths sources',
|
|
1398
|
+
fn: pathsFn,
|
|
1399
|
+
},
|
|
1272
1400
|
},
|
|
1273
1401
|
},
|
|
1274
1402
|
{
|
|
1275
1403
|
name: 'workflow',
|
|
1404
|
+
description: 'read the workflows the project declares (there is no workflow engine yet)',
|
|
1276
1405
|
operations: {
|
|
1277
1406
|
workflowList: {
|
|
1278
1407
|
name: 'workflowList',
|
|
1279
1408
|
mutates: false,
|
|
1409
|
+
description: 'print the workflow manifest (workflows.yaml) and every workflow it includes, with their phases',
|
|
1410
|
+
example: 'wingfoil workflow list',
|
|
1280
1411
|
fn: wrapReadOnly(loaders_1.loadWorkflowsYaml),
|
|
1281
1412
|
},
|
|
1282
1413
|
},
|
|
@@ -1296,6 +1427,7 @@ exports.CORE_MODULES = [
|
|
|
1296
1427
|
// the concurrently-edited `directives` block; `enumerateOperations` sorts, so position is inert.
|
|
1297
1428
|
{
|
|
1298
1429
|
name: 'directive',
|
|
1430
|
+
description: 'create, assign and remove custom directives',
|
|
1299
1431
|
operations: {
|
|
1300
1432
|
// The FIRST Directives-pillar mutation (P3.1) — `mutates: true`, so by construction an MCP Tool
|
|
1301
1433
|
// (`directive.create`) + CLI command (`wingfoil directive create`), and the third op the
|
|
@@ -1305,7 +1437,9 @@ exports.CORE_MODULES = [
|
|
|
1305
1437
|
directiveCreate: {
|
|
1306
1438
|
name: 'directiveCreate',
|
|
1307
1439
|
mutates: true,
|
|
1308
|
-
|
|
1440
|
+
description: 'create a custom directive from a scaffold',
|
|
1441
|
+
options: [{ name: 'name', required: true, valueName: 'name', description: 'the new directive, written to .wingfoil/directives/custom/<name>.md' }],
|
|
1442
|
+
example: 'wingfoil directive create --name api-style',
|
|
1309
1443
|
fn: directiveCreateFn,
|
|
1310
1444
|
},
|
|
1311
1445
|
// P3.2 (task-051-directive-assign) — on this SINGULAR module per dl-041 B, so `deriveVerb` yields
|
|
@@ -1313,10 +1447,12 @@ exports.CORE_MODULES = [
|
|
|
1313
1447
|
directiveAssign: {
|
|
1314
1448
|
name: 'directiveAssign',
|
|
1315
1449
|
mutates: true,
|
|
1450
|
+
description: 'assign one or more directives to a role in roles.yaml',
|
|
1316
1451
|
options: [
|
|
1317
|
-
{ name: 'directive', required: true },
|
|
1318
|
-
{ name: 'role', required: true },
|
|
1452
|
+
{ name: 'directive', required: true, valueName: 'name[,name...]', description: 'the directive(s) to assign, comma-separated' },
|
|
1453
|
+
{ name: 'role', required: true, valueName: 'role', description: 'the role to assign them to, as the committed dna.yaml declares it' },
|
|
1319
1454
|
],
|
|
1455
|
+
example: 'wingfoil directive assign --directive api-style --role developer',
|
|
1320
1456
|
fn: directiveAssignFn,
|
|
1321
1457
|
},
|
|
1322
1458
|
// P3.3 (task-052-directive-remove) — on this SINGULAR module per dl-041 B, so `deriveVerb`
|
|
@@ -1324,7 +1460,14 @@ exports.CORE_MODULES = [
|
|
|
1324
1460
|
// no flags and no value options: the name rides the bare `positional` seam (spec-008 §7), like
|
|
1325
1461
|
// `memory submit <id>`. Completes REQ-SEC-07 for the directive surface (clause (a) via
|
|
1326
1462
|
// task-042's `requireCustomAsset`, clause (b) via `checkUnreferenced` — dl-030).
|
|
1327
|
-
directiveRemove: {
|
|
1463
|
+
directiveRemove: {
|
|
1464
|
+
name: 'directiveRemove',
|
|
1465
|
+
mutates: true,
|
|
1466
|
+
description: 'delete a custom directive',
|
|
1467
|
+
positional: { name: 'name', required: true, description: 'the custom directive to delete (built-in directives cannot be removed)' },
|
|
1468
|
+
example: 'wingfoil directive remove api-style',
|
|
1469
|
+
fn: directiveRemoveFn,
|
|
1470
|
+
},
|
|
1328
1471
|
},
|
|
1329
1472
|
},
|
|
1330
1473
|
];
|