@pareto4/pp-core 1.12.1 → 1.17.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/.claude-plugin/plugin.json +3 -3
- package/README.md +26 -9
- package/agents/pp-code-reviewer.md +3 -2
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +7 -21
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +274 -0
- package/agents/pp-verifier.md +7 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-review.md +2 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +35 -0
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +14 -19
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +526 -22
- package/pp-core/bin/lib/capability-state.cjs +160 -4
- package/pp-core/bin/lib/capability-writer.cjs +19 -5
- package/pp-core/bin/lib/check-command-router.cjs +3867 -274
- package/pp-core/bin/lib/clusters.cjs +4 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -28
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +253 -76
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +154 -62
- package/pp-core/bin/lib/decisions.cjs +104 -27
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -1
- package/pp-core/bin/lib/git-base-branch.cjs +138 -10
- package/pp-core/bin/lib/git-provider.cjs +4 -4
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +653 -629
- package/pp-core/bin/lib/init-interactive.cjs +77 -21
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +8 -0
- package/pp-core/bin/lib/loop-resolver.cjs +169 -47
- package/pp-core/bin/lib/org-profile.cjs +544 -53
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -23
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -203
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/practices-review-check.cjs +15 -9
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-audit.cjs +2594 -0
- package/pp-core/bin/lib/vault-command-router.cjs +2756 -60
- package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
- package/pp-core/bin/lib/vault-index.cjs +699 -0
- package/pp-core/bin/lib/vault-mode.cjs +235 -0
- package/pp-core/bin/lib/vault-practices.cjs +1194 -89
- package/pp-core/bin/lib/vault-review.cjs +2102 -0
- package/pp-core/bin/lib/vault-seal.cjs +1936 -0
- package/pp-core/bin/lib/vault-standards.cjs +1505 -0
- package/pp-core/bin/lib/vault-trace.cjs +3184 -0
- package/pp-core/bin/lib/vault-update.cjs +242 -0
- package/pp-core/bin/lib/vault-write-back.cjs +78 -31
- package/pp-core/bin/lib/vault.cjs +2713 -634
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +184 -37
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +1 -0
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -14
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-practices.md +31 -3
- package/pp-core/references/planner-vault-standards.md +320 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/practice-arithmetic-contract.md +121 -0
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/references/verifier-practice-verdicts.md +263 -0
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +202 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/templates/practices/README.md +137 -11
- package/pp-core/templates/project-charter.md +226 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -0
- package/pp-core/workflows/audit-fix.md +63 -0
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -1
- package/pp-core/workflows/code-review.md +10 -1
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +56 -2
- package/pp-core/workflows/discovery-sprint/prefill.md +21 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -48
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +9 -1
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -18
- package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
- package/pp-core/workflows/execute-phase.md +154 -110
- package/pp-core/workflows/execute-plan.md +58 -4
- package/pp-core/workflows/fast.md +53 -0
- package/pp-core/workflows/help/modes/full.md +1 -0
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +99 -43
- package/pp-core/workflows/quick.md +120 -2
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -24
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +60 -1
- package/pp-core/workflows/vault-review.md +325 -0
- package/pp-core/workflows/verify-work.md +13 -12
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +199 -0
- package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +13 -2
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -15
- package/scripts/record-vault-mode-golden.cjs +89 -0
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/vault-calibrate.cjs +996 -0
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -472
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/phase-tier.cjs +0 -467
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -72
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -34
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- package/scripts/lint-org-coordinates.cjs +0 -426
|
@@ -29,9 +29,14 @@ const { VALID_PROFILES, getAgentToModelMapForProfile, formatAgentToModelMapAsTab
|
|
|
29
29
|
const configSchema = require("./config-schema.cjs");
|
|
30
30
|
const { VALID_CONFIG_KEYS, isValidConfigKey, capabilityEnumValues } = configSchema;
|
|
31
31
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
|
+
const capabilityActivation = require("./capability-activation.cjs");
|
|
34
|
+
// GATE-05: reuse the activation resolver's flat-form lookup rather than growing
|
|
35
|
+
// a second copy of the rule here. capability-activation.cjs depends only on
|
|
36
|
+
// planning-workspace.cjs and node builtins, so this import adds no cycle.
|
|
37
|
+
const { _getFlatConfigValue } = capabilityActivation;
|
|
32
38
|
const review_reviewer_selection_cjs_1 = require("./review-reviewer-selection.cjs");
|
|
33
39
|
const configuration_cjs_1 = require("./configuration.cjs");
|
|
34
|
-
const phase_tier_cjs_1 = require("./phase-tier.cjs");
|
|
35
40
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
36
41
|
const CONFIG_KEY_SUGGESTIONS = {
|
|
37
42
|
'workflow.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
|
@@ -465,6 +470,20 @@ function setConfigValues(cwd, entries) {
|
|
|
465
470
|
}
|
|
466
471
|
});
|
|
467
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* The complete `init.probe_status` vocabulary, as `pp-tools config-set`
|
|
475
|
+
* enforces it (07-11 / WR-04).
|
|
476
|
+
*
|
|
477
|
+
* The vocabulary's OWNER is `INIT_PROBE_STATUS` in `init-contract.cts`, which
|
|
478
|
+
* is where the record writers (`--record-probe` / `--defer-probe`) take it
|
|
479
|
+
* from. This is a deliberate copy, not a re-export: `config.cts` is a leaf and
|
|
480
|
+
* `init-contract.cjs` pulls in capability-activation, compose-bindings and
|
|
481
|
+
* org-profile, so importing it here would introduce a leaf-to-adapter edge.
|
|
482
|
+
* What keeps the copy honest is the two-direction parity test in
|
|
483
|
+
* `tests/config.test.cjs` — widening either vocabulary without the other
|
|
484
|
+
* reddens it.
|
|
485
|
+
*/
|
|
486
|
+
const VALID_INIT_PROBE_STATUSES = Object.freeze(['passed', 'failed', 'deferred']);
|
|
468
487
|
/**
|
|
469
488
|
* The ONE membership test every enum guard in `cmdConfigSet` asks (13-REVIEW
|
|
470
489
|
* CR-01). The raw value is parsed before the guards — `true`/`false`/numbers
|
|
@@ -485,14 +504,21 @@ function isStringMember(list, value) {
|
|
|
485
504
|
* (`"FALSE"`, `"no"`, `"off"`) or a number. `capability-activation`'s
|
|
486
505
|
* `_resolveActivationValue` ends in `Boolean(r.value)`, so those strings read as
|
|
487
506
|
* ON — an operator typing `config-set vault.enabled FALSE` to switch the vault
|
|
488
|
-
* off instead ARMED
|
|
489
|
-
*
|
|
507
|
+
* off instead ARMED the two `blocking: true` / `onError: halt` gates
|
|
508
|
+
* `capabilities/vault` hangs on that key, against CLAUDE.md's constraint that a
|
|
509
|
+
* `vault.enabled=false` project stays completely inert.
|
|
490
510
|
*
|
|
491
|
-
* The list, not
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
511
|
+
* The list, not eleven copies of the same `if`: the first eight are every key
|
|
512
|
+
* that gates a blocking hook through that `Boolean()` read, and
|
|
513
|
+
* `tests/config.test.cjs` DERIVES that requirement from the shipped capability
|
|
514
|
+
* registry, so a future blocking gate on a new key reddens until it joins.
|
|
515
|
+
* `vault.required` is the recorded 02-SECURITY UF-01 instance of the same
|
|
516
|
+
* defect: its READ path already refuses a non-boolean (`booleanPolicyValue` in
|
|
517
|
+
* `src/org-profile.cts`), but the CLI door still answered `{"updated": true}` for a
|
|
518
|
+
* value the reader would then discard — the exact "reports success, changes
|
|
519
|
+
* nothing" shape UF-01 was opened for. The last three were already guarded
|
|
520
|
+
* individually and are folded in here unchanged, message included, so there is
|
|
521
|
+
* one mechanism in this file rather than two.
|
|
496
522
|
*
|
|
497
523
|
* REFUSE, do not normalise: `FALSE` plainly means false, but `1`, `enabled` and
|
|
498
524
|
* `on` are guesses, and a config-set that silently rewrites the operator's word
|
|
@@ -501,8 +527,8 @@ function isStringMember(list, value) {
|
|
|
501
527
|
*/
|
|
502
528
|
const BOOLEAN_ONLY_CONFIG_KEYS = Object.freeze([
|
|
503
529
|
'vault.enabled',
|
|
504
|
-
'vault.required',
|
|
505
530
|
'vault.practices_enabled',
|
|
531
|
+
'vault.required',
|
|
506
532
|
'workflow.schema_drift_gate',
|
|
507
533
|
'workflow.nyquist_validation',
|
|
508
534
|
'workflow.security_enforcement',
|
|
@@ -578,10 +604,6 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
578
604
|
if (kp === 'ship.pr_body_sections') {
|
|
579
605
|
validateShipPrBodySections(parsedValue);
|
|
580
606
|
}
|
|
581
|
-
// Phase ceremony tier (pack 9) — enum, src/phase-tier.cts owns the list
|
|
582
|
-
if (kp === 'phase.tier' && !isStringMember(phase_tier_cjs_1.PHASE_TIERS, parsedValue)) {
|
|
583
|
-
error(`Invalid phase.tier '${val}'. Valid values: ${phase_tier_cjs_1.PHASE_TIERS.join(', ')}`, ERROR_REASON.CONFIG_INVALID_VALUE);
|
|
584
|
-
}
|
|
585
607
|
// Human verification checkpoint mode (#3309)
|
|
586
608
|
const VALID_HUMAN_VERIFY_MODES = ['mid-flight', 'end-of-phase'];
|
|
587
609
|
if (kp === 'workflow.human_verify_mode' && !isStringMember(VALID_HUMAN_VERIFY_MODES, parsedValue)) {
|
|
@@ -601,14 +623,27 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
601
623
|
if (kp === 'code_quality.fallow.profile' && !isStringMember(VALID_FALLOW_PROFILES, parsedValue)) {
|
|
602
624
|
error(`Invalid code_quality.fallow.profile '${val}'. Valid values: ${VALID_FALLOW_PROFILES.join(', ')}`);
|
|
603
625
|
}
|
|
626
|
+
// init.probe_status enum validation (07-11 / WR-04). 07-02 registered the
|
|
627
|
+
// four `init.probe_*` keys so config-set would not reject them as unknown;
|
|
628
|
+
// two shipped descriptions then claimed an out-of-vocabulary value "is
|
|
629
|
+
// refused at write time", which was true only of the record writers. This
|
|
630
|
+
// guard makes the claim true on this path too. Membership is tested on the
|
|
631
|
+
// parsed value's TYPE as well (13-REVIEW CR-01): `true`/`false`/numbers are
|
|
632
|
+
// coerced above and a `[`/`{` value is JSON-parsed, so only a plain string
|
|
633
|
+
// can be a member. A VALID value set here is still accepted — that is T-07-09's disclosed
|
|
634
|
+
// accepted residual, not an oversight.
|
|
635
|
+
if (kp === 'init.probe_status' && !isStringMember(VALID_INIT_PROBE_STATUSES, parsedValue)) {
|
|
636
|
+
error(`Invalid init.probe_status '${val}'. Valid values: ${VALID_INIT_PROBE_STATUSES.join(', ')}`, ERROR_REASON.USAGE);
|
|
637
|
+
}
|
|
604
638
|
// plan_review.source_grounding_authority (#22) — enum
|
|
605
639
|
const VALID_SOURCE_GROUNDING_AUTHORITIES = ['grep', 'intel', 'treesitter', 'lsp', 'scip'];
|
|
606
640
|
if (kp === 'plan_review.source_grounding_authority' && !isStringMember(VALID_SOURCE_GROUNDING_AUTHORITIES, parsedValue)) {
|
|
607
641
|
error(`Invalid plan_review.source_grounding_authority '${val}'. Valid values: ${VALID_SOURCE_GROUNDING_AUTHORITIES.join(', ')}`);
|
|
608
642
|
}
|
|
609
|
-
// Capability enum keys, in ONE place (13-05, D-07). Measured
|
|
610
|
-
// `config-set workflow.code_review_depth bogus`
|
|
611
|
-
//
|
|
643
|
+
// Capability enum keys, in ONE place (Vault Assurance 13-05, D-07). Measured
|
|
644
|
+
// 2026-10-09: `config-set workflow.code_review_depth bogus` and
|
|
645
|
+
// `config-set vault.mode whatever` both wrote at exit 0, because no guard
|
|
646
|
+
// consulted the registry's declared `values`. This runs AFTER every
|
|
612
647
|
// hand-coded guard above, so a key that already has its own guard
|
|
613
648
|
// (`workflow.drift_action`) keeps its message byte-identical; the
|
|
614
649
|
// registry, through `capabilityEnumValues`, is the only list consulted
|
|
@@ -618,8 +653,9 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
618
653
|
// The TYPE is checked before membership (13-REVIEW CR-01): a value starting
|
|
619
654
|
// with `[` is JSON-parsed above, and `String(['standards']) === 'standards'`,
|
|
620
655
|
// so a membership-only test let a one-element array through and wrote it —
|
|
621
|
-
// which `
|
|
622
|
-
//
|
|
656
|
+
// which `resolveVaultMode` then refused on every vault command, and which
|
|
657
|
+
// `federated-config` silently reads back as the default for the other enum
|
|
658
|
+
// keys. Every declared capability enum value is a string, so a non-string
|
|
623
659
|
// parse (array, object, number, boolean) can never be a member.
|
|
624
660
|
const enumValues = capabilityEnumValues(kp);
|
|
625
661
|
if (enumValues && !isStringMember(enumValues, parsedValue)) {
|
|
@@ -651,8 +687,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
651
687
|
output(maskedResult, raw, `${kp}=${masked}`);
|
|
652
688
|
return;
|
|
653
689
|
}
|
|
654
|
-
//
|
|
655
|
-
// replaced) is display-safe.
|
|
690
|
+
// VAULT-EGRESS-1: the written value stays raw; the echo of it (and of the value it replaced) is display-safe.
|
|
656
691
|
const shownValue = redactConfigCoordinates(kp, parsedValue);
|
|
657
692
|
const shownResult = {
|
|
658
693
|
...setConfigValueResult,
|
|
@@ -662,15 +697,16 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
662
697
|
output(shownResult, raw, `${kp}=${String(shownValue)}`);
|
|
663
698
|
}
|
|
664
699
|
/**
|
|
665
|
-
*
|
|
666
|
-
* for the config
|
|
667
|
-
* `SHARED_REPO_CONFIG_KEY`). `keyPath`
|
|
668
|
-
* at (`''` for the whole config): the
|
|
669
|
-
* carries it, is projected through
|
|
670
|
-
* src/vault.cts); every other value is
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
700
|
+
* VAULT-EGRESS-1 (quick 261005-8py gap closure): the display-safe form of a
|
|
701
|
+
* config value about to be EMITTED, for the one config key that is a remote
|
|
702
|
+
* repo coordinate (the shared-tier repo, `SHARED_REPO_CONFIG_KEY`). `keyPath`
|
|
703
|
+
* is the dotted path the value was read at (`''` for the whole config): the
|
|
704
|
+
* key itself, or any ancestor whose object carries it, is projected through
|
|
705
|
+
* the ONE owner (`redactCloneTarget`, `src/vault.cts`); every other value is
|
|
706
|
+
* returned untouched. The file on disk and every in-process reader keep the
|
|
707
|
+
* raw value — this runs at emission only, the same posture `isSecretKey`
|
|
708
|
+
* masking already takes on this surface. The key is read from its owner
|
|
709
|
+
* module rather than written here (this file names no allowlisted key).
|
|
674
710
|
*/
|
|
675
711
|
function redactConfigCoordinates(keyPath, value) {
|
|
676
712
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -688,7 +724,7 @@ function redactConfigCoordinates(keyPath, value) {
|
|
|
688
724
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
689
725
|
return value;
|
|
690
726
|
const copy = JSON.parse(JSON.stringify(value));
|
|
691
|
-
//
|
|
727
|
+
// GATE-05's flat form: the whole dotted key written as one top-level property.
|
|
692
728
|
if (at.length === 0 && typeof copy[SHARED_REPO_CONFIG_KEY] === 'string') {
|
|
693
729
|
copy[SHARED_REPO_CONFIG_KEY] = safe(copy[SHARED_REPO_CONFIG_KEY]);
|
|
694
730
|
}
|
|
@@ -712,23 +748,77 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
|
712
748
|
}
|
|
713
749
|
// After the error() guard, keyPath is narrowed to string.
|
|
714
750
|
const kp = keyPath;
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
751
|
+
/**
|
|
752
|
+
* 02-04: the fall-back tail shared by every `config-get` miss.
|
|
753
|
+
*
|
|
754
|
+
* PRECEDENCE — project config > org profile > `--default` > SCHEMA_DEFAULTS >
|
|
755
|
+
* error. This helper is only ever reached AFTER both dotted forms of the
|
|
756
|
+
* project read have missed, so project config outranking the profile is
|
|
757
|
+
* structural here rather than a comparison someone has to keep correct. That
|
|
758
|
+
* ordering is T-02-17's mitigation and is load-bearing: a fleet-wide profile
|
|
759
|
+
* must never be able to overrule what the project itself configured.
|
|
760
|
+
*
|
|
761
|
+
* Wiring this closes the gap `resolveVaultRequired` documents — the vault
|
|
762
|
+
* policy key's workflow line reads it through `config-get`, so before this
|
|
763
|
+
* existed a profile-seeded value reached that resolver's callers but never
|
|
764
|
+
* that workflow. (The key is named in `org-profile.cts`, not here: this file
|
|
765
|
+
* stays free of any allowlisted key literal so the "resolution is generic"
|
|
766
|
+
* check stays a meaningful grep.)
|
|
767
|
+
*
|
|
768
|
+
* NO KEY IS NAMED HERE, deliberately. `readProfileValue` self-gates on
|
|
769
|
+
* ORG_PROFILE_ALLOWLIST and does zero filesystem access for a key that is not
|
|
770
|
+
* on it, so consulting it on every miss is free for the overwhelmingly common
|
|
771
|
+
* non-allowlisted key and the wiring stays generic — adding a key to that
|
|
772
|
+
* allowlist is sufficient to make it readable here.
|
|
773
|
+
*
|
|
774
|
+
* Warnings go to fd 2 only; stdout stays byte-clean so `--raw` still emits a
|
|
775
|
+
* bare scalar (UIBR-02).
|
|
776
|
+
*/
|
|
777
|
+
const missFallback = (missMessage, missReason) => {
|
|
778
|
+
// Call-time lazy require, matching org-profile.cts's own use of the pattern
|
|
779
|
+
// for git-provider.cjs. org-profile.cjs does not import config.cjs, so this
|
|
780
|
+
// adds no cycle — keep it that way.
|
|
781
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
782
|
+
const orgProfile = require('./org-profile.cjs');
|
|
783
|
+
let profileValue;
|
|
784
|
+
try {
|
|
785
|
+
const profile = orgProfile.readProfileValue(cwd, kp);
|
|
786
|
+
profileValue = profile.value;
|
|
787
|
+
for (const w of profile.warnings)
|
|
788
|
+
console.error('Warning: ' + w);
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
// A malformed or unreadable profile must never take down a config read
|
|
792
|
+
// that would otherwise have resolved a default. Degrade to "no profile".
|
|
793
|
+
profileValue = undefined;
|
|
794
|
+
}
|
|
795
|
+
if (profileValue !== undefined) {
|
|
796
|
+
const shownProfileValue = redactConfigCoordinates(kp, profileValue);
|
|
797
|
+
output(shownProfileValue, raw, String(shownProfileValue));
|
|
798
|
+
return;
|
|
719
799
|
}
|
|
720
|
-
|
|
721
|
-
|
|
800
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
801
|
+
if (hasDefault) {
|
|
722
802
|
output(defaultValue, raw, String(defaultValue));
|
|
723
803
|
return;
|
|
724
804
|
}
|
|
725
|
-
|
|
805
|
+
if (Object.prototype.hasOwnProperty.call(SCHEMA_DEFAULTS, kp)) {
|
|
726
806
|
const def = SCHEMA_DEFAULTS[kp];
|
|
727
807
|
output(def, raw, String(def));
|
|
728
808
|
return;
|
|
729
809
|
}
|
|
810
|
+
error(missMessage, missReason);
|
|
811
|
+
};
|
|
812
|
+
let config = {};
|
|
813
|
+
try {
|
|
814
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
815
|
+
config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
816
|
+
}
|
|
730
817
|
else {
|
|
731
|
-
|
|
818
|
+
// No config.json at all: the profile still gets its turn before the
|
|
819
|
+
// defaults, or a config-less project could never see a profile value.
|
|
820
|
+
missFallback('No config.json found at ' + configPath, ERROR_REASON.CONFIG_NO_FILE);
|
|
821
|
+
return;
|
|
732
822
|
}
|
|
733
823
|
}
|
|
734
824
|
catch (err) {
|
|
@@ -736,37 +826,35 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
|
736
826
|
throw err;
|
|
737
827
|
error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
738
828
|
}
|
|
829
|
+
// GATE-05: the same dotted key may be written flat — one top-level property
|
|
830
|
+
// whose name contains dots — which the nested walk below cannot see. Resolved
|
|
831
|
+
// here as a FALLBACK only: the walk runs first and, when it finds a value,
|
|
832
|
+
// this is never consulted. Nested therefore wins whenever both forms are
|
|
833
|
+
// present, matching capability-activation.cjs's getConfigValueEitherForm so
|
|
834
|
+
// the two read paths a gate uses answer identically. The rejected-segment and
|
|
835
|
+
// hasOwnProperty guards live in that shared helper.
|
|
836
|
+
const flatFallback = _getFlatConfigValue(config, kp);
|
|
739
837
|
// Traverse dot-notation path (e.g., "workflow.auto_advance")
|
|
740
838
|
const keys = kp.split('.');
|
|
741
839
|
let current = config;
|
|
742
840
|
for (const key of keys) {
|
|
743
841
|
if (current === undefined || current === null || typeof current !== 'object') {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
if (Object.prototype.hasOwnProperty.call(SCHEMA_DEFAULTS, kp)) {
|
|
750
|
-
const def = SCHEMA_DEFAULTS[kp];
|
|
751
|
-
output(def, raw, String(def));
|
|
752
|
-
return;
|
|
842
|
+
if (flatFallback.found) {
|
|
843
|
+
current = flatFallback.value;
|
|
844
|
+
break;
|
|
753
845
|
}
|
|
754
|
-
|
|
846
|
+
missFallback(`Key not found: ${kp}`, ERROR_REASON.CONFIG_KEY_NOT_FOUND);
|
|
847
|
+
return;
|
|
755
848
|
}
|
|
756
849
|
current = current[key];
|
|
757
850
|
}
|
|
851
|
+
// GATE-05: the walk completed but resolved nothing — flat form gets its turn.
|
|
852
|
+
if (current === undefined && flatFallback.found) {
|
|
853
|
+
current = flatFallback.value;
|
|
854
|
+
}
|
|
758
855
|
if (current === undefined) {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
output(defaultValue, raw, String(defaultValue));
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
if (Object.prototype.hasOwnProperty.call(SCHEMA_DEFAULTS, kp)) {
|
|
765
|
-
const def = SCHEMA_DEFAULTS[kp];
|
|
766
|
-
output(def, raw, String(def));
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
error(`Key not found: ${kp}`, ERROR_REASON.CONFIG_KEY_NOT_FOUND);
|
|
856
|
+
missFallback(`Key not found: ${kp}`, ERROR_REASON.CONFIG_KEY_NOT_FOUND);
|
|
857
|
+
return;
|
|
770
858
|
}
|
|
771
859
|
// Never echo plaintext for sensitive keys via config-get. Plaintext lives
|
|
772
860
|
// in config.json on disk; the CLI surface always shows the masked form.
|
|
@@ -775,7 +863,10 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
|
775
863
|
output(masked, raw, masked);
|
|
776
864
|
return;
|
|
777
865
|
}
|
|
778
|
-
//
|
|
866
|
+
// Pre-existing behaviour, now explicit: an object-valued key under --raw
|
|
867
|
+
// stringifies to '[object Object]'. Replacing the trailing `error()` (typed
|
|
868
|
+
// `never`) with the shared miss-tail cost TypeScript the narrowing that used
|
|
869
|
+
// to keep this quiet; the emitted bytes are unchanged.
|
|
779
870
|
const shown = redactConfigCoordinates(kp, current);
|
|
780
871
|
output(shown, raw, String(shown));
|
|
781
872
|
}
|
|
@@ -880,6 +971,7 @@ function cmdMigrateConfig(cwd, raw) {
|
|
|
880
971
|
}
|
|
881
972
|
module.exports = {
|
|
882
973
|
VALID_CONFIG_KEYS,
|
|
974
|
+
VALID_INIT_PROBE_STATUSES,
|
|
883
975
|
BOOLEAN_ONLY_CONFIG_KEYS,
|
|
884
976
|
cmdConfigEnsureSection,
|
|
885
977
|
cmdConfigSet,
|
|
@@ -890,7 +982,7 @@ module.exports = {
|
|
|
890
982
|
cmdMigrateConfig,
|
|
891
983
|
// Exported for programmatic use by capability-writer and tests
|
|
892
984
|
setConfigValue,
|
|
893
|
-
// Pack 6 D-07: `state load` emits the whole config through the same projection.
|
|
894
|
-
redactConfigCoordinates,
|
|
895
985
|
setConfigValues,
|
|
986
|
+
// VAULT-EGRESS-1: `state load` emits the whole config through the same projection.
|
|
987
|
+
redactConfigCoordinates,
|
|
896
988
|
};
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* CJS callers that only use {id, text} safely ignore the extra fields.
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.parseDecisionsReport = parseDecisionsReport;
|
|
13
14
|
exports.parseDecisions = parseDecisions;
|
|
14
15
|
const DISCRETION_HEADINGS = new Set([
|
|
15
16
|
"claude's discretion",
|
|
@@ -17,6 +18,67 @@ const DISCRETION_HEADINGS = new Set([
|
|
|
17
18
|
'claude discretion',
|
|
18
19
|
]);
|
|
19
20
|
const NON_TRACKABLE_TAGS = new Set(['informational', 'folded', 'deferred']);
|
|
21
|
+
// ── The decision-bullet grammar (260910-8ea) ────────────────────────────────
|
|
22
|
+
//
|
|
23
|
+
// Why a grammar rather than one regex: this repository writes THREE bullet
|
|
24
|
+
// forms, and until 260910-8ea only the first was readable. The two others were
|
|
25
|
+
// not exotic — they are what `discuss-phase` emits and what hand-authored
|
|
26
|
+
// CONTEXT.md files use — so a parser that reads only form A hands a blocking
|
|
27
|
+
// coverage gate an empty decision set and lets it report a clean pass. That is
|
|
28
|
+
// the defect this grammar retires (todo: decision-coverage-gate-false-pass).
|
|
29
|
+
//
|
|
30
|
+
// Form A `- **D-NN:** text` → arm A
|
|
31
|
+
// Form B `- **D-NN: Title.** text` → arm B (title may wrap: arm D)
|
|
32
|
+
// Form C `- **D-NN** text` → arm C
|
|
33
|
+
//
|
|
34
|
+
// HEAD matches the bullet opener and splits off `rest` (everything after the id
|
|
35
|
+
// and its optional `[tags]` group). The id rule is #3575's, unchanged: the first
|
|
36
|
+
// character after `D-` must be alphanumeric, so `D--x` and `D-_x` still fail
|
|
37
|
+
// HEAD and reach the parse-miss guard.
|
|
38
|
+
const BULLET_HEAD_RE = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?(.*)$/;
|
|
39
|
+
// Arm A — form A, widened. The run before the FIRST `:**` may now contain `:`.
|
|
40
|
+
// #1343 wrote that run as `[^:*]*`, which is what dropped every bold-title
|
|
41
|
+
// bullet AND `- **D-07 ratio 3:1:** w` (that miss used to be pinned as a floor;
|
|
42
|
+
// it now parses as D-07 / "w"). Lazy `[^*]*?` still stops at the first `:**`.
|
|
43
|
+
const ARM_A_RE = /^([^*]*?):\*\*\s*(.*)$/;
|
|
44
|
+
// Arm B — form B, the bold title. The title MAY contain `:` because real
|
|
45
|
+
// titles carry backticked loop points (`plan:post`, `execute:post`, `check:`);
|
|
46
|
+
// a title regex that excluded `:` measured four misses across two phases.
|
|
47
|
+
const ARM_B_RE = /^([^*]*?):\s*([^*]*?)\s*\*\*\s*(.*)$/;
|
|
48
|
+
// Arm C — the bold closes with no colon before it (`- **D-VH-1** body`, and
|
|
49
|
+
// `- **D-13 → see D-14.** *(reserved)*`). The only thing this form lacks is
|
|
50
|
+
// punctuation; the id is unambiguous, so it is admitted.
|
|
51
|
+
const ARM_C_RE = /^([^*]*?)\s*\*\*\s*(.*)$/;
|
|
52
|
+
// Arm D — no closing `**` on this line: a form-B title that wraps onto the
|
|
53
|
+
// next line. The existing continuation logic appends the following indented
|
|
54
|
+
// lines, stray `**` included (they are cosmetic in the resulting text).
|
|
55
|
+
const ARM_D_RE = /^([^*]*)$/;
|
|
56
|
+
/**
|
|
57
|
+
* Read a decision bullet's text out of `rest` (the remainder of the bullet line
|
|
58
|
+
* after the id and optional `[tags]`), trying arms A → B → C → D; first match
|
|
59
|
+
* wins. Returns null when no arm matches, which sends the line to the
|
|
60
|
+
* parse-miss guard.
|
|
61
|
+
*
|
|
62
|
+
* The guard is NOT vacuous after this widening: a malformed id fails HEAD, and
|
|
63
|
+
* a `rest` carrying a `*` in no arm shape (`- **D-01 *italic* no close`)
|
|
64
|
+
* matches no arm.
|
|
65
|
+
*/
|
|
66
|
+
function readBulletText(rest) {
|
|
67
|
+
const a = rest.match(ARM_A_RE);
|
|
68
|
+
if (a)
|
|
69
|
+
return a[2];
|
|
70
|
+
const b = rest.match(ARM_B_RE);
|
|
71
|
+
// Headline first, so a six-word `softPhrase` is drawn from the title.
|
|
72
|
+
if (b)
|
|
73
|
+
return (b[2] + ' ' + b[3]).trim();
|
|
74
|
+
const c = rest.match(ARM_C_RE);
|
|
75
|
+
if (c)
|
|
76
|
+
return (c[1] + ' ' + c[2]).trim();
|
|
77
|
+
const d = rest.match(ARM_D_RE);
|
|
78
|
+
if (d)
|
|
79
|
+
return d[1].replace(/^\s*:?\s*/, '').trim();
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
20
82
|
/**
|
|
21
83
|
* Strip fenced code blocks from `content` so example `<decisions>` snippets
|
|
22
84
|
* inside ```` ``` ```` do not pollute the parser (review F11).
|
|
@@ -40,34 +102,28 @@ function extractDecisionsBlock(content) {
|
|
|
40
102
|
return matches.map((m) => m[1]).join('\n\n');
|
|
41
103
|
}
|
|
42
104
|
/**
|
|
43
|
-
* Parse
|
|
105
|
+
* Parse CONTEXT.md content into BOTH channels: the decisions that were read,
|
|
106
|
+
* and the bullets that could not be.
|
|
44
107
|
*
|
|
45
|
-
*
|
|
46
|
-
* non-trackable ones, with `trackable: false`).
|
|
47
|
-
*
|
|
108
|
+
* `decisions` holds ALL D-NN decisions found inside `<decisions>` (including
|
|
109
|
+
* non-trackable ones, with `trackable: false`). `unparsed` holds every line
|
|
110
|
+
* that opened like a decision bullet and matched no arm of the grammar.
|
|
111
|
+
*
|
|
112
|
+
* The second channel exists because "the block was empty" and "the parser
|
|
113
|
+
* could not read any of it" are opposite situations that a coverage gate used
|
|
114
|
+
* to report identically — as a clean pass on a total of zero (260910-8ea).
|
|
48
115
|
*/
|
|
49
|
-
function
|
|
116
|
+
function parseDecisionsReport(content) {
|
|
50
117
|
if (!content || typeof content !== 'string')
|
|
51
|
-
return [];
|
|
118
|
+
return { decisions: [], unparsed: [] };
|
|
52
119
|
const block = extractDecisionsBlock(content);
|
|
53
120
|
if (block === null)
|
|
54
|
-
return [];
|
|
121
|
+
return { decisions: [], unparsed: [] };
|
|
55
122
|
const lines = block.split(/\r?\n/);
|
|
56
123
|
const out = [];
|
|
124
|
+
const unparsed = [];
|
|
57
125
|
let category = '';
|
|
58
126
|
let inDiscretion = false;
|
|
59
|
-
// Bullet line: `- **D-NN[ [tags]]:** text`
|
|
60
|
-
// Phase 6 (#3575): aligned to CJS regex — accepts alphanumeric IDs (D-01, D-INFRA-01, D-FOO_BAR)
|
|
61
|
-
// in addition to numeric-only IDs (D-42). The first character after `D-` must
|
|
62
|
-
// be alphanumeric, so malformed shapes like `D--foo` or `D-_bar` are rejected.
|
|
63
|
-
// CJS callers consume {id, text} and ignore the optional extras.
|
|
64
|
-
// #1343: `[^:*]*` replaces the old `\s*` before `:**` so that a freeform run
|
|
65
|
-
// such as `(parenthetical)`, an em-dash, or other prose between the optional
|
|
66
|
-
// bracket-tag group and the closing `:**` is tolerated rather than silently
|
|
67
|
-
// dropping the whole decision. `[^:*]*` subsumes plain whitespace and stops
|
|
68
|
-
// correctly at `:**`. Capture groups 1 (id), 2 (bracket tags), 3 (text) are
|
|
69
|
-
// unchanged.
|
|
70
|
-
const bulletRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^:*]*:\*\*\s*(.*)$/;
|
|
71
127
|
let current = null;
|
|
72
128
|
const flush = () => {
|
|
73
129
|
if (current) {
|
|
@@ -94,29 +150,36 @@ function parseDecisions(content) {
|
|
|
94
150
|
inDiscretion = DISCRETION_HEADINGS.has(normalized);
|
|
95
151
|
continue;
|
|
96
152
|
}
|
|
97
|
-
const
|
|
98
|
-
|
|
153
|
+
const headMatch = line.match(BULLET_HEAD_RE);
|
|
154
|
+
const bulletText = headMatch ? readBulletText(headMatch[3]) : null;
|
|
155
|
+
if (headMatch && bulletText !== null) {
|
|
99
156
|
flush();
|
|
100
|
-
const id = `D-${
|
|
101
|
-
const tags =
|
|
102
|
-
?
|
|
157
|
+
const id = `D-${headMatch[1]}`;
|
|
158
|
+
const tags = headMatch[2]
|
|
159
|
+
? headMatch[2]
|
|
103
160
|
.split(',')
|
|
104
161
|
.map((t) => t.trim().toLowerCase())
|
|
105
162
|
.filter(Boolean)
|
|
106
163
|
: [];
|
|
107
164
|
const trackable = !inDiscretion && !tags.some((t) => NON_TRACKABLE_TAGS.has(t));
|
|
108
|
-
current = { id, text:
|
|
165
|
+
current = { id, text: bulletText, category, tags, trackable };
|
|
109
166
|
continue;
|
|
110
167
|
}
|
|
111
168
|
// Parse-miss guard (#1343): a line that looks like a `D-NN` decision bullet
|
|
112
|
-
// but failed
|
|
169
|
+
// but failed HEAD (malformed id) or matched no arm of the grammar must NOT
|
|
113
170
|
// be silently dropped — a narrowed trackable set lets a blocking coverage gate
|
|
114
171
|
// report a false pass. Surface it loudly instead.
|
|
172
|
+
//
|
|
173
|
+
// 260910-8ea: the miss is now recorded as DATA as well, so a gate can fail
|
|
174
|
+
// on it rather than warning past it. The `console.warn` stays: the stderr
|
|
175
|
+
// signal is what a human running the command by hand sees, and it is pinned
|
|
176
|
+
// by the #1343 regression tests.
|
|
115
177
|
if (/^\s*-\s+\*\*D-/.test(line)) {
|
|
116
178
|
// A malformed D-bullet still starts a (failed) new decision, so it ends the
|
|
117
179
|
// previous one — flush before warning so a following continuation line cannot
|
|
118
180
|
// be mis-appended to the prior valid decision.
|
|
119
181
|
flush();
|
|
182
|
+
unparsed.push({ line: trimmed, category });
|
|
120
183
|
console.warn(`parseDecisions: ignored unparseable decision bullet: ${trimmed}`);
|
|
121
184
|
continue;
|
|
122
185
|
}
|
|
@@ -132,5 +195,19 @@ function parseDecisions(content) {
|
|
|
132
195
|
}
|
|
133
196
|
}
|
|
134
197
|
flush();
|
|
135
|
-
return out;
|
|
198
|
+
return { decisions: out, unparsed };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Parse trackable decisions from CONTEXT.md content.
|
|
202
|
+
*
|
|
203
|
+
* Returns ALL D-NN decisions found inside `<decisions>` (including
|
|
204
|
+
* non-trackable ones, with `trackable: false`). Callers that only want the
|
|
205
|
+
* gate-enforced decisions should filter `.filter(d => d.trackable)`.
|
|
206
|
+
*
|
|
207
|
+
* This signature is unchanged, and so is its behaviour. A caller that must not
|
|
208
|
+
* treat an unreadable bullet as an ABSENT decision — any gate that reports a
|
|
209
|
+
* verdict — should call `parseDecisionsReport` and read `unparsed`.
|
|
210
|
+
*/
|
|
211
|
+
function parseDecisions(content) {
|
|
212
|
+
return parseDecisionsReport(content).decisions;
|
|
136
213
|
}
|