devflow-kit 3.7.0 → 3.9.0
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/CHANGELOG.md +479 -1
- package/README.md +15 -15
- package/dist/agents/code.md +6 -8
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +7 -8
- package/dist/agents/git.md +3 -3
- package/dist/agents/harden.md +1 -1
- package/dist/agents/refactor.md +2 -4
- package/dist/agents/review.md +4 -4
- package/dist/agents/skim.md +2 -2
- package/dist/agents/triage.md +9 -2
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/curation-move-io.js +102 -0
- package/dist/cli/commands/curation-move-step.js +374 -0
- package/dist/cli/commands/curation.js +808 -0
- package/dist/cli/commands/gate-level-prompts.js +428 -0
- package/dist/cli/commands/gates.js +35 -11
- package/dist/cli/commands/init-seed.js +11 -8
- package/dist/cli/commands/init.js +244 -57
- package/dist/cli/commands/memory.js +60 -13
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +157 -17
- package/dist/cli.js +2 -2
- package/dist/commands/bug-analysis.md +25 -36
- package/dist/commands/code-review.md +40 -44
- package/dist/commands/debug.md +5 -5
- package/dist/commands/dynamic-build.md +26 -25
- package/dist/commands/dynamic-plan.md +5 -5
- package/dist/commands/dynamic-profile.md +5 -5
- package/dist/commands/dynamic-tickets.md +5 -5
- package/dist/commands/explore.md +6 -6
- package/dist/commands/implement.md +53 -82
- package/dist/commands/plan.md +6 -6
- package/dist/commands/release.md +5 -5
- package/dist/commands/research.md +6 -6
- package/dist/commands/resolve.md +39 -41
- package/dist/commands/self-review.md +5 -13
- package/dist/core/area-pause-notice.js +44 -0
- package/dist/core/assets.js +20 -20
- package/dist/core/curate-tuning-config.js +5 -6
- package/dist/core/curation-move-notice.js +84 -0
- package/dist/core/curation-move-skip.js +35 -0
- package/dist/core/curation-move.js +35 -0
- package/dist/core/curation-queue-cleanup.js +135 -0
- package/dist/core/curation-store.js +99 -0
- package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
- package/dist/core/echo-value.js +11 -5
- package/dist/core/evidence-policy.js +11 -9
- package/dist/core/feature-switch.js +95 -19
- package/dist/core/fs-atomic.js +1 -1
- package/dist/core/gate-level.js +10 -2
- package/dist/core/git.js +16 -14
- package/dist/core/ledger-root.js +82 -22
- package/dist/core/linked-path.js +4 -4
- package/dist/core/manifest-lock.js +238 -0
- package/dist/core/manifest.js +58 -14
- package/dist/core/migrations.js +1 -13
- package/dist/core/observations.js +2 -2
- package/dist/core/plugins.js +19 -19
- package/dist/core/pool.js +26 -0
- package/dist/core/project-paths.js +103 -34
- package/dist/core/queue-drain.js +2 -2
- package/dist/core/tracked-areas.js +159 -0
- package/dist/core/tracker.js +1 -1
- package/dist/{learning-off → curation-off}/agents/code.md +6 -8
- package/dist/{learning-off → curation-off}/agents/fix.md +7 -8
- package/dist/{learning-off → curation-off}/agents/refactor.md +2 -4
- package/dist/{learning-off → curation-off}/agents/review.md +3 -3
- package/dist/{learning-off → curation-off}/agents/triage.md +8 -1
- package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
- package/dist/{learning-off → curation-off}/commands/code-review.md +37 -41
- package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +23 -22
- package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
- package/dist/{learning-off → curation-off}/commands/implement.md +50 -79
- package/dist/{learning-off → curation-off}/commands/plan.md +2 -2
- package/dist/{learning-off → curation-off}/commands/resolve.md +36 -38
- package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
- package/dist/hud/components/{learning-counts.js → curation-counts.js} +63 -17
- package/dist/hud/config.js +1 -1
- package/dist/hud/index.js +6 -6
- package/dist/hud/render.js +3 -3
- package/dist/skills/git/references/pr/post-resolution-summary.md +12 -11
- package/dist/skills/git/references/pr/post-review-summary.md +12 -11
- package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
- package/dist/skills/git/references/pr/validate-branch.md +8 -5
- package/dist/skills/git/references/publication-gate.md +6 -7
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +13 -5
- package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
- package/dist/targets/claude-code/{learning-install.js → curation-install.js} +85 -65
- package/dist/targets/claude-code/installer.js +15 -15
- package/dist/targets/claude-code/language-stamp.js +5 -5
- package/dist/targets/claude-code/post-install.js +2 -2
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +3 -2
- package/src/assets/agents/_partials/_code.mds +11 -11
- package/src/assets/agents/code.mds +6 -6
- package/src/assets/agents/curate.md +8 -8
- package/src/assets/agents/debug.mds +6 -6
- package/src/assets/agents/design.mds +18 -18
- package/src/assets/agents/evaluate.md +6 -12
- package/src/assets/agents/fix.mds +7 -6
- package/src/assets/agents/git.mds +3 -3
- package/src/assets/agents/harden.mds +7 -7
- package/src/assets/agents/knowledge.mds +23 -23
- package/src/assets/agents/refactor.mds +2 -2
- package/src/assets/agents/research.mds +15 -15
- package/src/assets/agents/review.mds +43 -43
- package/src/assets/agents/skim.mds +11 -11
- package/src/assets/agents/triage.mds +29 -22
- package/src/assets/agents/validate.md +30 -50
- package/src/assets/commands/_partials/_decisions.mds +22 -20
- package/src/assets/commands/_partials/_engine.mds +8 -8
- package/src/assets/commands/_partials/_knowledge.mds +29 -15
- package/src/assets/commands/_partials/_preamble.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +3 -3
- package/src/assets/commands/_partials/_wave.mds +7 -7
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +53 -57
- package/src/assets/commands/debug.mds +14 -14
- package/src/assets/commands/dynamic-build.mds +38 -37
- package/src/assets/commands/dynamic-plan.mds +22 -22
- package/src/assets/commands/dynamic-profile.mds +2 -2
- package/src/assets/commands/dynamic-tickets.mds +8 -8
- package/src/assets/commands/explore.mds +20 -20
- package/src/assets/commands/implement.mds +77 -98
- package/src/assets/commands/plan.mds +27 -27
- package/src/assets/commands/release.mds +12 -12
- package/src/assets/commands/research.mds +19 -19
- package/src/assets/commands/resolve.mds +66 -60
- package/src/assets/commands/self-review.mds +20 -20
- package/src/assets/mds/git/_ops.mds +9 -2
- package/src/assets/mds/git/_ops_pr.mds +21 -11
- package/src/assets/mds/git/_ops_steps.mds +20 -4
- package/src/assets/mds/git/_pr.mds +21 -18
- package/src/assets/mds/git/_references.mds +6 -7
- package/src/assets/mds/tracker/_github.mds +1 -4
- package/src/assets/mds/tracker/_jira.mds +1 -4
- package/src/assets/mds/tracker/_linear.mds +1 -4
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +389 -92
- package/src/assets/scripts/hooks/background-memory-update +81 -3
- package/src/assets/scripts/hooks/capture-prompt +12 -12
- package/src/assets/scripts/hooks/capture-question +11 -11
- package/src/assets/scripts/hooks/capture-turn +12 -12
- package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
- package/src/assets/scripts/hooks/curation-move +293 -0
- package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
- package/src/assets/scripts/hooks/ensure-proxy +4 -4
- package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
- package/src/assets/scripts/hooks/git-marker +81 -0
- package/src/assets/scripts/hooks/json-helper.cjs +176 -51
- package/src/assets/scripts/hooks/json-parse +1 -1
- package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
- package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +308 -122
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
- package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +42 -22
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +452 -0
- package/src/assets/scripts/hooks/memory-permission +10 -2
- package/src/assets/scripts/hooks/memory-worker +12 -1
- package/src/assets/scripts/hooks/pre-compact-memory +17 -16
- package/src/assets/scripts/hooks/queue-append +444 -102
- package/src/assets/scripts/hooks/resolve-project-root +2 -2
- package/src/assets/scripts/hooks/session-start-context +198 -72
- package/src/assets/scripts/hooks/session-start-memory +42 -43
- package/src/assets/scripts/lib/project-config.cjs +12 -5
- package/src/assets/scripts/pr-evidence.cjs +5 -2
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +283 -45
- package/src/assets/scripts/verify-evidence.cjs +307 -75
- package/src/assets/skills/agent-references/SKILL.md +11 -2
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +29 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +25 -0
- package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
- package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -0
- package/src/assets/skills/apply-decisions/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +7 -7
- package/src/assets/skills/docs-framework/references/patterns.md +3 -4
- package/src/assets/skills/quality-gates/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
- package/templates/lint-pack/.lint-pack.conf +86 -0
- package/templates/lint-pack/README.md +190 -0
- package/templates/lint-pack/lint-pack.sh +905 -0
- package/dist/cli/commands/learning.js +0 -554
- package/dist/core/learning-queue-cleanup.js +0 -27
- package/dist/core/learning-store.js +0 -61
- /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// `features.gateLevel` default
|
|
23
23
|
//
|
|
24
24
|
// D-SETTINGS-LOCAL-ONLY: local git subprocesses only, and never gh, never the
|
|
25
|
-
// network, never a git command that refreshes the index. At most
|
|
25
|
+
// network, never a git command that refreshes the index. At most these, in order:
|
|
26
26
|
// git rev-parse --show-toplevel always
|
|
27
27
|
// git --no-literal-pathspecs -c core.fsmonitor=false ls-files -z -- <PERSONAL_TRACKING_PATHSPECS>
|
|
28
28
|
// only when config.json exists;
|
|
@@ -32,6 +32,17 @@
|
|
|
32
32
|
// git symbolic-ref --quiet refs/remotes/origin/HEAD in a repository
|
|
33
33
|
// git cat-file blob refs/remotes/origin/<D>:.devflow/project.json
|
|
34
34
|
// only when origin/HEAD names D
|
|
35
|
+
// git rev-parse --path-format=absolute --git-common-dir only when MEMORY or CURATION is
|
|
36
|
+
// still on and <toplevel>/.git is
|
|
37
|
+
// not a folder: it names the ledger root
|
|
38
|
+
// git ... ls-files -z -- <the pathspecs of an area> only for a switch still on: ONE call
|
|
39
|
+
// for both areas when their roots are
|
|
40
|
+
// equal, one more per area only when it
|
|
41
|
+
// lists something; otherwise one per root
|
|
42
|
+
// MEMORY=off while git tracks the memory folder at the toplevel (or does not answer for it),
|
|
43
|
+
// and CURATION=off while git tracks the curation folders at the ledger root (or does not
|
|
44
|
+
// answer for them). The answer only narrows a switch; the leaf lib hooks/lib/tracked-areas.cjs
|
|
45
|
+
// holds the pathspecs, the scrub of redirecting variables and the mapping of git's answer.
|
|
35
46
|
// The team's evidence FLOOR is the default branch's and is
|
|
36
47
|
// resolve-evidence-policy.cjs's job (it runs over the network, from commands).
|
|
37
48
|
// This script reads the WORKTREE's copy for every other key, so a branch that
|
|
@@ -56,7 +67,7 @@
|
|
|
56
67
|
// TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default>
|
|
57
68
|
// TRACKER_WARN=<none|mismatch|invalid> SITE=<url|none> KEY=<KEY|none>
|
|
58
69
|
// REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|id,…>
|
|
59
|
-
// MEMORY=<on|off>
|
|
70
|
+
// MEMORY=<on|off> CURATION=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>
|
|
60
71
|
// (one line on stdout; wrapped here for reading). Every value is a closed token
|
|
61
72
|
// or a SITE/KEY that passed its shape gate, so no other byte of a config file can
|
|
62
73
|
// reach stdout. Whole-file rule: a project.json or config.json that EXISTS but
|
|
@@ -92,6 +103,7 @@ const fs = require('fs');
|
|
|
92
103
|
const path = require('path');
|
|
93
104
|
const childProcess = require('child_process');
|
|
94
105
|
const projectConfig = require('./lib/project-config.cjs');
|
|
106
|
+
const trackedAreas = require('./hooks/lib/tracked-areas.cjs');
|
|
95
107
|
|
|
96
108
|
const {
|
|
97
109
|
PUBLICATIONS,
|
|
@@ -114,7 +126,7 @@ const {
|
|
|
114
126
|
/** @typedef {'off' | 'auto' | 'full'} Publication */
|
|
115
127
|
/** @typedef {'full' | 'standard' | 'lean'} GateLevel */
|
|
116
128
|
/** @typedef {'personal' | 'machine' | 'default'} GateLevelSource */
|
|
117
|
-
/** @typedef {'memory' | '
|
|
129
|
+
/** @typedef {'memory' | 'curation' | 'knowledge'} FeatureSwitch */
|
|
118
130
|
/** @typedef {'machine' | 'project' | 'personal'} SwitchSource */
|
|
119
131
|
|
|
120
132
|
/** Who decided TRACKER. `default` is github with no layer naming a provider. */
|
|
@@ -175,7 +187,7 @@ const SETTINGS_LINE_RE = new RegExp(
|
|
|
175
187
|
+ ' REVIEW_PUBLICATION=(?<publication>' + PUBLICATIONS.join('|') + ')'
|
|
176
188
|
+ ' COMPLIANCE=(?<compliance>off|generic|(?:' + COMPLIANCE_IDS.join('|') + ')(?:,(?:'
|
|
177
189
|
+ COMPLIANCE_IDS.join('|') + ')){0,' + String(COMPLIANCE_IDS.length - 1) + '})'
|
|
178
|
-
+ ' MEMORY=(?<memory>on|off)
|
|
190
|
+
+ ' MEMORY=(?<memory>on|off) CURATION=(?<curation>on|off) KNOWLEDGE=(?<knowledge>on|off)'
|
|
179
191
|
+ ' GATE_LEVEL=(?<gateLevel>' + GATE_LEVELS.join('|') + ')$',
|
|
180
192
|
);
|
|
181
193
|
|
|
@@ -188,15 +200,14 @@ const SETTINGS_LINE_RE = new RegExp(
|
|
|
188
200
|
* REVIEW_PUBLICATION=off nothing is published (raised to a stub under required)
|
|
189
201
|
* COMPLIANCE=generic the review lens still runs
|
|
190
202
|
* KNOWLEDGE=off no knowledge write-back commits into the repository
|
|
191
|
-
* MEMORY/
|
|
192
|
-
*
|
|
193
|
-
* unreadable file as narrowing nothing)
|
|
203
|
+
* MEMORY/CURATION=off a resolver that cannot say which repository it is in cannot
|
|
204
|
+
* tell whether git tracks their folders, so both stay off
|
|
194
205
|
* GATE_LEVEL=full the most checking applies: a resolver that cannot read
|
|
195
206
|
* its inputs never lowers the level
|
|
196
207
|
*/
|
|
197
208
|
const SETTINGS_FAIL_CLOSED_LINE =
|
|
198
209
|
'TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none '
|
|
199
|
-
+ 'REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=
|
|
210
|
+
+ 'REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=off CURATION=off KNOWLEDGE=off GATE_LEVEL=full';
|
|
200
211
|
|
|
201
212
|
/** Bound on each local git call. */
|
|
202
213
|
const GIT_TIMEOUT_MS = 5000;
|
|
@@ -216,15 +227,16 @@ const BLOB_MAX_BUFFER = MAX_CONFIG_BYTES + 1;
|
|
|
216
227
|
/** The personal file, repository-relative, in git's spelling. */
|
|
217
228
|
const PERSONAL_REL = '.devflow/config.json';
|
|
218
229
|
/**
|
|
219
|
-
* The spellings a case-folding file system opens as
|
|
220
|
-
*
|
|
221
|
-
* macOS default) compares names by full Unicode case
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* point on APFS finds no other one that folds into these
|
|
225
|
-
* ignores. The code is written with escapes, so each code point
|
|
230
|
+
* The spellings a case-folding file system opens as `config.json` beyond ASCII letter
|
|
231
|
+
* case (the leaf lib's DEVFLOW_SPELLINGS lists those of `.devflow`). git's `icase` folds
|
|
232
|
+
* ASCII letters only, but APFS (the macOS default) compares names by full Unicode case
|
|
233
|
+
* folding, so it also opens `config.json` (U+FB01 folds to "fi") and `config.jſon`
|
|
234
|
+
* (U+017F folds to "s") at the personal path, as it opens `.devflow` (U+FB02 folds to
|
|
235
|
+
* "fl"). A probe of every code point on APFS finds no other one that folds into these
|
|
236
|
+
* names and none that it ignores. The code is written with escapes, so each code point
|
|
237
|
+
* is named.
|
|
226
238
|
*/
|
|
227
|
-
const DEVFLOW_SPELLINGS =
|
|
239
|
+
const { DEVFLOW_SPELLINGS } = trackedAreas;
|
|
228
240
|
const CONFIG_SPELLINGS = Object.freeze(['config.json', 'con\u{FB01}g.json', 'config.j\u{17F}on', 'con\u{FB01}g.j\u{17F}on']);
|
|
229
241
|
/**
|
|
230
242
|
* The pathspecs of the tracking check (D-PERSONAL-UNTRACKED), each matched
|
|
@@ -257,8 +269,10 @@ const PROJECT_REL = '.devflow/project.json';
|
|
|
257
269
|
|
|
258
270
|
/**
|
|
259
271
|
* @typedef {{ on: boolean, source: SwitchSource }} SwitchState
|
|
260
|
-
* `source` is the layer that decided: `machine` when the machine switch is off
|
|
261
|
-
*
|
|
272
|
+
* `source` is the layer that decided: `machine` when the machine switch is off,
|
|
273
|
+
* when git tracks the area's folder or does not answer for it (a switch narrowed
|
|
274
|
+
* that way carries source `machine`, so it has no file to name), or when nothing
|
|
275
|
+
* narrowed it; else the repo layer whose literal `false` narrowed it.
|
|
262
276
|
*
|
|
263
277
|
* @typedef {{
|
|
264
278
|
* ok: boolean,
|
|
@@ -269,7 +283,7 @@ const PROJECT_REL = '.devflow/project.json';
|
|
|
269
283
|
* key: string | null,
|
|
270
284
|
* reviewPublication: Publication,
|
|
271
285
|
* compliance: { enabled: boolean, frameworks: readonly string[] },
|
|
272
|
-
* switches: { memory: SwitchState,
|
|
286
|
+
* switches: { memory: SwitchState, curation: SwitchState, knowledge: SwitchState },
|
|
273
287
|
* gateLevel: GateLevel,
|
|
274
288
|
* gateLevelSource: GateLevelSource | null,
|
|
275
289
|
* gateLevelInvalid: boolean,
|
|
@@ -340,8 +354,8 @@ const FAIL_CLOSED_SETTINGS = Object.freeze({
|
|
|
340
354
|
reviewPublication: 'off',
|
|
341
355
|
compliance: Object.freeze({ enabled: true, frameworks: Object.freeze([]) }),
|
|
342
356
|
switches: Object.freeze({
|
|
343
|
-
memory: Object.freeze({ on:
|
|
344
|
-
|
|
357
|
+
memory: Object.freeze({ on: false, source: /** @type {SwitchSource} */ ('machine') }),
|
|
358
|
+
curation: Object.freeze({ on: false, source: /** @type {SwitchSource} */ ('machine') }),
|
|
345
359
|
knowledge: Object.freeze({ on: false, source: /** @type {SwitchSource} */ ('machine') }),
|
|
346
360
|
}),
|
|
347
361
|
gateLevel: /** @type {GateLevel} */ ('full'),
|
|
@@ -371,13 +385,28 @@ function isPlainObject(value) {
|
|
|
371
385
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
372
386
|
}
|
|
373
387
|
|
|
388
|
+
/**
|
|
389
|
+
* The keys a switch is stored under, current names first (feature-switch.ts
|
|
390
|
+
* CURRENT_KEYS). `curation` is read under `curation` and its pre-rename copy
|
|
391
|
+
* `learning` (#529).
|
|
392
|
+
*/
|
|
393
|
+
const CURRENT_SWITCH_KEYS = Object.freeze({
|
|
394
|
+
memory: Object.freeze(['memory']),
|
|
395
|
+
curation: Object.freeze(['curation', 'learning']),
|
|
396
|
+
knowledge: Object.freeze(['knowledge']),
|
|
397
|
+
});
|
|
398
|
+
|
|
374
399
|
/** The pre-rename key each switch was stored under (feature-switch.ts LEGACY_KEYS). */
|
|
375
|
-
const LEGACY_SWITCH_KEYS = Object.freeze({
|
|
400
|
+
const LEGACY_SWITCH_KEYS = Object.freeze({ curation: 'decisions', knowledge: 'kb' });
|
|
376
401
|
|
|
377
402
|
/**
|
|
378
|
-
* src/core/feature-switch.ts isMachineFeatureOn exactly
|
|
379
|
-
*
|
|
380
|
-
*
|
|
403
|
+
* src/core/feature-switch.ts isMachineFeatureOn exactly. Off when any current
|
|
404
|
+
* key is a boolean `false`; else on when any current key is a boolean `true`;
|
|
405
|
+
* else, when neither is a boolean, the pre-rename key decides if it is a
|
|
406
|
+
* boolean; else on. A non-boolean value is ignored for that key only, so a
|
|
407
|
+
* disagreeing pair resolves to off. Anything that is not a manifest-shaped
|
|
408
|
+
* object is on. The capture hooks' gate read without jq calls this one, so the
|
|
409
|
+
* rule is held here alone.
|
|
381
410
|
*
|
|
382
411
|
* @param {unknown} manifest
|
|
383
412
|
* @param {FeatureSwitch} feature
|
|
@@ -387,9 +416,10 @@ function machineSwitchOn(manifest, feature) {
|
|
|
387
416
|
if (!isPlainObject(manifest)) return true;
|
|
388
417
|
const features = manifest.features;
|
|
389
418
|
if (!isPlainObject(features)) return true;
|
|
419
|
+
const current = CURRENT_SWITCH_KEYS[feature].map(key => features[key]).filter(value => typeof value === 'boolean');
|
|
420
|
+
if (current.length > 0) return !current.includes(false);
|
|
390
421
|
const legacy = /** @type {Record<string, string>} */ (LEGACY_SWITCH_KEYS)[feature];
|
|
391
|
-
|
|
392
|
-
return value !== false;
|
|
422
|
+
return legacy === undefined || features[legacy] !== false;
|
|
393
423
|
}
|
|
394
424
|
|
|
395
425
|
/**
|
|
@@ -425,7 +455,7 @@ function machineCompliance(manifest) {
|
|
|
425
455
|
* src/core/gate-level.ts machineGateLevel exactly, and total: null when no level
|
|
426
456
|
* is recorded (not an object, `features` not an object, or no own `gateLevel`
|
|
427
457
|
* key), `invalid` when the key is present and is not one of the three words,
|
|
428
|
-
* else the level. The
|
|
458
|
+
* else the level. The curation-capture hook folds a manifest of any shape, so
|
|
429
459
|
* this must never throw.
|
|
430
460
|
*
|
|
431
461
|
* @param {unknown} manifest
|
|
@@ -625,6 +655,11 @@ function foldCompliance(project, manifest, defaultBranch) {
|
|
|
625
655
|
* narrows: absent, malformed, duplicated and unreadable all leave the switch as
|
|
626
656
|
* the machine set it. No repo layer can turn on what the machine turned off.
|
|
627
657
|
*
|
|
658
|
+
* A switch with two current keys (`curation`: `curation` and `learning`, #529)
|
|
659
|
+
* is a narrow-only union: a literal `false` in either key of a file narrows it,
|
|
660
|
+
* and each key is classified on its own, so a duplicate or a non-boolean value
|
|
661
|
+
* in one key leaves the other readable.
|
|
662
|
+
*
|
|
628
663
|
* @param {any} project
|
|
629
664
|
* @param {any} personal
|
|
630
665
|
* @param {unknown} manifest
|
|
@@ -635,7 +670,8 @@ function foldSwitch(project, personal, manifest, feature) {
|
|
|
635
670
|
/** @param {any} file */
|
|
636
671
|
const narrows = file => {
|
|
637
672
|
const features = fieldIn(file, 'features', 'absent');
|
|
638
|
-
|
|
673
|
+
if (features.kind !== 'valid') return false;
|
|
674
|
+
return CURRENT_SWITCH_KEYS[feature].some(key => features.value[key].kind === 'valid' && features.value[key].value === false);
|
|
639
675
|
};
|
|
640
676
|
if (!machineSwitchOn(manifest, feature)) return Object.freeze({ on: false, source: 'machine' });
|
|
641
677
|
if (narrows(project)) return Object.freeze({ on: false, source: 'project' });
|
|
@@ -683,7 +719,9 @@ function foldGateLevel(personal, manifest) {
|
|
|
683
719
|
* The whole-file rule's resolution (D-SETTINGS-LINE): the fail-closed values,
|
|
684
720
|
* naming the layer whose file could not be read, except the compliance lens —
|
|
685
721
|
* a broken file affects only the keys it owns, so the lens stays the fold of
|
|
686
|
-
* every readable layer (D-LENS-UNION)
|
|
722
|
+
* every readable layer (D-LENS-UNION) — and the memory and curation switches,
|
|
723
|
+
* which stay as the machine set them (an unreadable file narrows nothing, and
|
|
724
|
+
* the area answers narrow them afterwards). An unreadable project.json declares
|
|
687
725
|
* nothing readable of its own (repoCompliance null) but reads as a malformed
|
|
688
726
|
* declaration, so its lens is at least `generic`; an unreadable config.json
|
|
689
727
|
* owns no compliance at all, so the worktree project.json's ids still count.
|
|
@@ -695,8 +733,11 @@ function foldGateLevel(personal, manifest) {
|
|
|
695
733
|
function unreadableSettings(layer, inputs) {
|
|
696
734
|
const defaultBranch = inputs.defaultBranch === undefined ? ABSENT_FIELD : inputs.defaultBranch;
|
|
697
735
|
const lens = foldCompliance(inputs.project, inputs.manifest, defaultBranch);
|
|
736
|
+
/** @param {FeatureSwitch} feature @returns {SwitchState} */
|
|
737
|
+
const machine = feature => Object.freeze({ on: machineSwitchOn(inputs.manifest, feature), source: 'machine' });
|
|
698
738
|
return Object.freeze({
|
|
699
739
|
...FAIL_CLOSED_SETTINGS,
|
|
740
|
+
switches: Object.freeze({ ...FAIL_CLOSED_SETTINGS.switches, memory: machine('memory'), curation: machine('curation') }),
|
|
700
741
|
// The level stays `full` with no deciding layer, but a readable manifest still shows its own value.
|
|
701
742
|
gateLevelMachine: machineGateLevel(inputs.manifest),
|
|
702
743
|
compliance: lens.compliance,
|
|
@@ -724,7 +765,7 @@ function foldSettings(inputs) {
|
|
|
724
765
|
compliance: lens.compliance,
|
|
725
766
|
switches: Object.freeze({
|
|
726
767
|
memory: foldSwitch(project, personal, manifest, 'memory'),
|
|
727
|
-
|
|
768
|
+
curation: foldSwitch(project, personal, manifest, 'curation'),
|
|
728
769
|
knowledge: foldSwitch(project, personal, manifest, 'knowledge'),
|
|
729
770
|
}),
|
|
730
771
|
...foldGateLevel(personal, manifest),
|
|
@@ -736,6 +777,19 @@ function foldSettings(inputs) {
|
|
|
736
777
|
});
|
|
737
778
|
}
|
|
738
779
|
|
|
780
|
+
/**
|
|
781
|
+
* The settings for the layers as read: the whole-file rule's resolution for the first layer
|
|
782
|
+
* that could not be read (project before personal), else the fold of the layers.
|
|
783
|
+
*
|
|
784
|
+
* @param {SettingsInputs} inputs
|
|
785
|
+
* @returns {Settings}
|
|
786
|
+
*/
|
|
787
|
+
function settingsOfLayers(inputs) {
|
|
788
|
+
if (inputs.project.kind === 'invalid') return unreadableSettings('project', inputs);
|
|
789
|
+
if (inputs.personal.kind === 'invalid') return unreadableSettings('personal', inputs);
|
|
790
|
+
return foldSettings(inputs);
|
|
791
|
+
}
|
|
792
|
+
|
|
739
793
|
// ---------------------------------------------------------------------------
|
|
740
794
|
// Gathering the layers (the imperative shell — D-SETTINGS-LOCAL-ONLY)
|
|
741
795
|
// ---------------------------------------------------------------------------
|
|
@@ -751,18 +805,20 @@ function defaultExec(file, args, opts) {
|
|
|
751
805
|
|
|
752
806
|
/**
|
|
753
807
|
* One bounded local git call, normalized. The argv is a fresh copy, so an exec
|
|
754
|
-
* cannot mutate a caller's constant.
|
|
808
|
+
* cannot mutate a caller's constant. `baseEnv` (default: the process environment)
|
|
809
|
+
* is the environment the call's own derives from.
|
|
755
810
|
*
|
|
756
811
|
* @param {ExecFn} exec
|
|
757
812
|
* @param {string} cwd
|
|
758
813
|
* @param {readonly string[]} args
|
|
759
814
|
* @param {number} maxBuffer
|
|
815
|
+
* @param {NodeJS.ProcessEnv} [baseEnv]
|
|
760
816
|
* @returns {GitAnswer}
|
|
761
817
|
*/
|
|
762
|
-
function runGit(exec, cwd, args, maxBuffer) {
|
|
818
|
+
function runGit(exec, cwd, args, maxBuffer, baseEnv) {
|
|
763
819
|
const res = exec('git', [...args], {
|
|
764
820
|
cwd,
|
|
765
|
-
env: Object.assign({}, process.env, { GIT_TERMINAL_PROMPT: '0' }),
|
|
821
|
+
env: Object.assign({}, baseEnv === undefined ? process.env : baseEnv, { GIT_TERMINAL_PROMPT: '0' }),
|
|
766
822
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
767
823
|
timeout: GIT_TIMEOUT_MS,
|
|
768
824
|
maxBuffer,
|
|
@@ -827,7 +883,8 @@ function gitToplevel(exec, dir) {
|
|
|
827
883
|
* so it is unreadable and its keys fail closed
|
|
828
884
|
* `--no-literal-pathspecs` leads the argv: an inherited GIT_LITERAL_PATHSPECS=1
|
|
829
885
|
* would read every pathspec as a plain path, magic included, list nothing and
|
|
830
|
-
* read as untracked.
|
|
886
|
+
* read as untracked. The call runs without the variables that make git read
|
|
887
|
+
* another repository or index (the leaf lib's scrub), so it reads `root`'s own.
|
|
831
888
|
*
|
|
832
889
|
* @param {ExecFn} exec
|
|
833
890
|
* @param {string} root
|
|
@@ -836,7 +893,8 @@ function gitToplevel(exec, dir) {
|
|
|
836
893
|
*/
|
|
837
894
|
function personalTracking(exec, root, rootConfirmed) {
|
|
838
895
|
const res = runGit(exec, root,
|
|
839
|
-
['--no-literal-pathspecs', '-c', 'core.fsmonitor=false', 'ls-files', '-z', '--', ...PERSONAL_TRACKING_PATHSPECS], LINE_MAX_BUFFER
|
|
896
|
+
['--no-literal-pathspecs', '-c', 'core.fsmonitor=false', 'ls-files', '-z', '--', ...PERSONAL_TRACKING_PATHSPECS], LINE_MAX_BUFFER,
|
|
897
|
+
trackedAreas.gitEnvWithoutRedirects(process.env));
|
|
840
898
|
if (!res.answered) return 'unknown';
|
|
841
899
|
if (res.stdout.length > 0) return 'tracked';
|
|
842
900
|
if (res.ok) return 'untracked';
|
|
@@ -931,13 +989,180 @@ function defaultBranchCompliance(exec, root) {
|
|
|
931
989
|
return fieldIn(projectConfig.parseProjectBytes(blob.stdout), 'compliance', 'malformed');
|
|
932
990
|
}
|
|
933
991
|
|
|
992
|
+
// ---------------------------------------------------------------------------
|
|
993
|
+
// The tracked areas: memory at the toplevel, curation at the ledger root
|
|
994
|
+
// ---------------------------------------------------------------------------
|
|
995
|
+
|
|
996
|
+
/** What git before 2.31 prints first for `--path-format=absolute`: the flag it does not know, echoed back. */
|
|
997
|
+
const PATH_FORMAT_ECHO = '--path-format=absolute';
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* How the `.git` entry at `root` stands, never followed: `folder`, `absent` (nothing there)
|
|
1001
|
+
* or `other` (a file, a link, or an entry that could not be probed).
|
|
1002
|
+
*
|
|
1003
|
+
* @param {string} root
|
|
1004
|
+
* @returns {'folder' | 'absent' | 'other'}
|
|
1005
|
+
*/
|
|
1006
|
+
function gitEntryKind(root) {
|
|
1007
|
+
try {
|
|
1008
|
+
return fs.lstatSync(path.join(root, '.git')).isDirectory() ? 'folder' : 'other';
|
|
1009
|
+
} catch (err) {
|
|
1010
|
+
const code = err && /** @type {{ code?: string }} */ (err).code;
|
|
1011
|
+
return code === 'ENOENT' || code === 'ENOTDIR' ? 'absent' : 'other';
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Whether the main checkout `main` holds the ledger: it has a `.devflow` folder (links
|
|
1017
|
+
* followed), its own `.git` entry, and is not HOME (a repository rooted at HOME is the
|
|
1018
|
+
* machine root). The paths are compared physically, as the hooks' df_is_project_root does;
|
|
1019
|
+
* an unresolvable `main` holds none, and an unset or unresolvable HOME refuses nothing.
|
|
1020
|
+
*
|
|
1021
|
+
* @param {string} main
|
|
1022
|
+
* @returns {boolean}
|
|
1023
|
+
*/
|
|
1024
|
+
function holdsLedger(main) {
|
|
1025
|
+
try {
|
|
1026
|
+
if (!fs.statSync(path.join(main, '.devflow')).isDirectory()) return false;
|
|
1027
|
+
fs.statSync(path.join(main, '.git'));
|
|
1028
|
+
const mainReal = fs.realpathSync(main);
|
|
1029
|
+
const home = process.env.HOME;
|
|
1030
|
+
if (typeof home !== 'string' || home === '') return true;
|
|
1031
|
+
try {
|
|
1032
|
+
return fs.realpathSync(home) !== mainReal;
|
|
1033
|
+
} catch (_) {
|
|
1034
|
+
return true;
|
|
1035
|
+
}
|
|
1036
|
+
} catch (_) {
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Where the curation ledger lives for the checkout whose toplevel git named: the rule the
|
|
1043
|
+
* hooks apply (DF_LEDGER_ROOT of df_resolve_roots in hooks/resolve-project-root), which the
|
|
1044
|
+
* TypeScript twin getLedgerRoot (src/core/ledger-root.ts) shares.
|
|
1045
|
+
* - `<toplevel>/.git` is a folder, or there is none: the toplevel, with no git call. (A
|
|
1046
|
+
* main checkout is its own ledger root; with no entry the leaf lib asks no git call.)
|
|
1047
|
+
* - `<toplevel>/.git` is a file or a link: one `git rev-parse --path-format=absolute
|
|
1048
|
+
* --git-common-dir`. A path ending in `/.git` whose parent holds the ledger
|
|
1049
|
+
* (holdsLedger) names the parent; any other path names the toplevel. A git before 2.31
|
|
1050
|
+
* echoes the flag back as its first line: the toplevel, as the hooks fall back to it.
|
|
1051
|
+
* - any other answer, or none: `unknown` (the caller reads curation as unanswered).
|
|
1052
|
+
* Never throws.
|
|
1053
|
+
*
|
|
1054
|
+
* @param {string} toplevel an absolute path git named as a work tree's top level
|
|
1055
|
+
* @param {{ exec?: ExecFn }} [deps]
|
|
1056
|
+
* @returns {{ kind: 'root', root: string } | { kind: 'unknown' }}
|
|
1057
|
+
*/
|
|
1058
|
+
function ledgerRootOf(toplevel, deps) {
|
|
1059
|
+
try {
|
|
1060
|
+
const exec = deps && typeof deps.exec === 'function' ? deps.exec : defaultExec;
|
|
1061
|
+
if (gitEntryKind(toplevel) !== 'other') return { kind: 'root', root: toplevel };
|
|
1062
|
+
const res = runGit(exec, toplevel, ['rev-parse', PATH_FORMAT_ECHO, '--git-common-dir'], LINE_MAX_BUFFER);
|
|
1063
|
+
if (!res.ok) return { kind: 'unknown' };
|
|
1064
|
+
const text = res.stdout.toString('utf8').replace(/\r?\n$/, '');
|
|
1065
|
+
if (text.split('\n', 1)[0] === PATH_FORMAT_ECHO) return { kind: 'root', root: toplevel };
|
|
1066
|
+
if (text === '' || /[\r\n\0]/.test(text) || !path.isAbsolute(text)) return { kind: 'unknown' };
|
|
1067
|
+
if (!text.endsWith('/.git')) return { kind: 'root', root: toplevel };
|
|
1068
|
+
const main = text.slice(0, -'/.git'.length);
|
|
1069
|
+
return { kind: 'root', root: main !== '' && holdsLedger(main) ? main : toplevel };
|
|
1070
|
+
} catch (_) {
|
|
1071
|
+
return { kind: 'unknown' };
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* @typedef {'tracked' | 'untracked' | 'unknown'} AreaAnswer
|
|
1077
|
+
* @typedef {{ memory: AreaAnswer, curation: AreaAnswer }} AreaAnswers
|
|
1078
|
+
*/
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* One area at one root, asked of the injected exec with the leaf lib's call (its pathspecs,
|
|
1082
|
+
* its scrub of redirecting variables, its bounds) and mapped by its judge. The leaf asks no
|
|
1083
|
+
* call, and answers `untracked`, for a root with no `.git` entry.
|
|
1084
|
+
*
|
|
1085
|
+
* @param {ExecFn} exec
|
|
1086
|
+
* @param {string} root
|
|
1087
|
+
* @param {'curation' | 'memory' | 'both'} area
|
|
1088
|
+
* @returns {AreaAnswer}
|
|
1089
|
+
*/
|
|
1090
|
+
function askArea(exec, root, area) {
|
|
1091
|
+
const call = trackedAreas.trackedAreaCall(root, area);
|
|
1092
|
+
if (!('argv' in call)) return call.answer;
|
|
1093
|
+
const res = exec('git', [...call.argv], {
|
|
1094
|
+
cwd: call.cwd,
|
|
1095
|
+
env: call.env,
|
|
1096
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1097
|
+
timeout: trackedAreas.TRACKED_AREAS_GIT_TIMEOUT_MS,
|
|
1098
|
+
maxBuffer: trackedAreas.TRACKED_AREAS_GIT_MAX_BUFFER,
|
|
1099
|
+
windowsHide: true,
|
|
1100
|
+
shell: false,
|
|
1101
|
+
});
|
|
1102
|
+
return trackedAreas.judgeTrackedAreas(res).answer;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* The answers for the areas asked about. An area that is not asked about reads `untracked`
|
|
1107
|
+
* (nothing to narrow). Equal roots: ONE call over both areas; nothing listed ends it, git
|
|
1108
|
+
* not answering reads both `unknown`, and only a listing earns one call per area to tell
|
|
1109
|
+
* them apart. Different roots: one call per root. A curation root that is null (the ledger
|
|
1110
|
+
* root could not be named) reads `unknown`.
|
|
1111
|
+
*
|
|
1112
|
+
* @param {ExecFn} exec
|
|
1113
|
+
* @param {string} memoryRoot
|
|
1114
|
+
* @param {string | null} curationRoot
|
|
1115
|
+
* @param {{ memory: boolean, curation: boolean }} asked
|
|
1116
|
+
* @returns {AreaAnswers}
|
|
1117
|
+
*/
|
|
1118
|
+
function trackedAreaAnswers(exec, memoryRoot, curationRoot, asked) {
|
|
1119
|
+
/** @type {AreaAnswers} */
|
|
1120
|
+
const answers = { memory: 'untracked', curation: 'untracked' };
|
|
1121
|
+
if (asked.memory && asked.curation && curationRoot === memoryRoot) {
|
|
1122
|
+
const union = askArea(exec, memoryRoot, 'both');
|
|
1123
|
+
if (union === 'untracked') return answers;
|
|
1124
|
+
if (union === 'unknown') return { memory: 'unknown', curation: 'unknown' };
|
|
1125
|
+
}
|
|
1126
|
+
if (asked.curation) answers.curation = curationRoot === null ? 'unknown' : askArea(exec, curationRoot, 'curation');
|
|
1127
|
+
if (asked.memory) answers.memory = askArea(exec, memoryRoot, 'memory');
|
|
1128
|
+
return answers;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* The settings with each switch whose area is not `untracked` off. Only a switch still on
|
|
1133
|
+
* is narrowed, and it carries source `machine`: no file of the repository turned it off, so
|
|
1134
|
+
* there is none to name. Answers `settings` itself when nothing changes.
|
|
1135
|
+
*
|
|
1136
|
+
* @param {Settings} settings
|
|
1137
|
+
* @param {AreaAnswers} answers
|
|
1138
|
+
* @returns {Settings}
|
|
1139
|
+
*/
|
|
1140
|
+
function narrowByAreas(settings, answers) {
|
|
1141
|
+
/** @param {'memory' | 'curation'} feature @returns {SwitchState} */
|
|
1142
|
+
const narrowed = feature => (answers[feature] === 'untracked' || !settings.switches[feature].on
|
|
1143
|
+
? settings.switches[feature]
|
|
1144
|
+
: Object.freeze({ on: false, source: 'machine' }));
|
|
1145
|
+
const memory = narrowed('memory');
|
|
1146
|
+
const curation = narrowed('curation');
|
|
1147
|
+
if (memory === settings.switches.memory && curation === settings.switches.curation) return settings;
|
|
1148
|
+
return Object.freeze({ ...settings, switches: Object.freeze({ ...settings.switches, memory, curation }) });
|
|
1149
|
+
}
|
|
1150
|
+
|
|
934
1151
|
/**
|
|
935
1152
|
* Resolve the settings for `opts.dir`. Never throws: a git that cannot say
|
|
936
1153
|
* whether `opts.dir` is in a repository, or any internal failure, is the
|
|
937
1154
|
* fail-closed resolution, which main() maps to exit 4. A repository file that
|
|
938
|
-
* exists but is unreadable fails every field closed but the compliance lens
|
|
939
|
-
* naming its layer in `unreadable` (the
|
|
940
|
-
* main() prints that line with exit 0.
|
|
1155
|
+
* exists but is unreadable fails every field closed but the compliance lens and
|
|
1156
|
+
* the memory and curation switches, naming its layer in `unreadable` (the
|
|
1157
|
+
* whole-file rule, D-SETTINGS-LINE); main() prints that line with exit 0.
|
|
1158
|
+
*
|
|
1159
|
+
* Every repository return is then narrowed by the areas: memory at the toplevel,
|
|
1160
|
+
* curation at the ledger root, each off while git tracks its folders or does not
|
|
1161
|
+
* answer for them. When git answers that `opts.dir` is no repository (a lookup
|
|
1162
|
+
* that fails for a reason such as a redirecting variable or a refusal for
|
|
1163
|
+
* ownership), the leaf lib's check still runs at `opts.dir`: with no `.git`
|
|
1164
|
+
* entry there it asks no git call and changes nothing; with one it asks a scrubbed
|
|
1165
|
+
* call, and an unanswered call reads both areas as unknown.
|
|
941
1166
|
*
|
|
942
1167
|
* @param {ResolveSettingsOptions} opts
|
|
943
1168
|
* @param {{ exec?: ExecFn }} [deps]
|
|
@@ -951,7 +1176,12 @@ function resolveSettings(opts, deps) {
|
|
|
951
1176
|
if (toplevel.kind === 'unknown') return FAIL_CLOSED_SETTINGS;
|
|
952
1177
|
if (toplevel.kind === 'none') {
|
|
953
1178
|
const none = projectConfig.parseProjectBytes(null);
|
|
954
|
-
|
|
1179
|
+
const folded = foldSettings({ project: none, personal: none, manifest, retiredPolicyFile: false });
|
|
1180
|
+
const start = path.resolve(opts.dir);
|
|
1181
|
+
return narrowByAreas(folded, trackedAreaAnswers(exec, start, start, {
|
|
1182
|
+
memory: folded.switches.memory.on,
|
|
1183
|
+
curation: folded.switches.curation.on,
|
|
1184
|
+
}));
|
|
955
1185
|
}
|
|
956
1186
|
const layers = readRepoLayers(toplevel.root, { exec, rootConfirmed: true });
|
|
957
1187
|
/** @type {SettingsInputs} */
|
|
@@ -963,9 +1193,14 @@ function resolveSettings(opts, deps) {
|
|
|
963
1193
|
defaultBranch: defaultBranchCompliance(exec, toplevel.root),
|
|
964
1194
|
retiredPolicyFile: existsNoFollow(path.join(toplevel.root, '.devflow', 'policy.json')),
|
|
965
1195
|
};
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1196
|
+
const folded = settingsOfLayers(inputs);
|
|
1197
|
+
const asked = { memory: folded.switches.memory.on, curation: folded.switches.curation.on };
|
|
1198
|
+
let curationRoot = null;
|
|
1199
|
+
if (asked.curation) {
|
|
1200
|
+
const ledger = ledgerRootOf(toplevel.root, { exec });
|
|
1201
|
+
curationRoot = ledger.kind === 'root' ? ledger.root : null;
|
|
1202
|
+
}
|
|
1203
|
+
return narrowByAreas(folded, trackedAreaAnswers(exec, toplevel.root, curationRoot, asked));
|
|
969
1204
|
} catch (_) {
|
|
970
1205
|
return FAIL_CLOSED_SETTINGS;
|
|
971
1206
|
}
|
|
@@ -1003,7 +1238,7 @@ function formatSettingsLine(s) {
|
|
|
1003
1238
|
+ ' REVIEW_PUBLICATION=' + s.reviewPublication
|
|
1004
1239
|
+ ' COMPLIANCE=' + complianceToken(s.compliance)
|
|
1005
1240
|
+ ' MEMORY=' + onOff(s.switches.memory)
|
|
1006
|
-
+ '
|
|
1241
|
+
+ ' CURATION=' + onOff(s.switches.curation)
|
|
1007
1242
|
+ ' KNOWLEDGE=' + onOff(s.switches.knowledge)
|
|
1008
1243
|
+ ' GATE_LEVEL=' + s.gateLevel;
|
|
1009
1244
|
}
|
|
@@ -1200,7 +1435,8 @@ if (require.main === module) {
|
|
|
1200
1435
|
}
|
|
1201
1436
|
|
|
1202
1437
|
// ---------------------------------------------------------------------------
|
|
1203
|
-
// Exports — the CLI seam (src/core/evidence-policy.ts)
|
|
1438
|
+
// Exports — the CLI seam (src/core/evidence-policy.ts), the capture hooks' gate read
|
|
1439
|
+
// without jq (hooks/queue-append requires machineSwitchOn) and the unit tests
|
|
1204
1440
|
// ---------------------------------------------------------------------------
|
|
1205
1441
|
|
|
1206
1442
|
module.exports = Object.freeze({
|
|
@@ -1214,6 +1450,8 @@ module.exports = Object.freeze({
|
|
|
1214
1450
|
foldGateLevel,
|
|
1215
1451
|
foldSettings,
|
|
1216
1452
|
readRepoLayers,
|
|
1453
|
+
machineSwitchOn,
|
|
1454
|
+
ledgerRootOf,
|
|
1217
1455
|
resolveSettings,
|
|
1218
1456
|
formatSettingsLine,
|
|
1219
1457
|
isCoherentSettingsLine,
|