devflow-kit 3.8.0 → 3.10.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 +231 -0
- package/README.md +6 -6
- package/dist/agents/code.md +10 -10
- package/dist/agents/fix.md +7 -7
- package/dist/agents/git.md +2 -2
- package/dist/agents/harden.md +7 -6
- package/dist/agents/refactor.md +6 -6
- package/dist/agents/skim.md +1 -1
- package/dist/agents/triage.md +7 -0
- package/dist/cli/commands/ambient.js +10 -2
- package/dist/cli/commands/compliance-prompts.js +9 -4
- package/dist/cli/commands/compliance.js +171 -24
- package/dist/cli/commands/curation.js +79 -23
- package/dist/cli/commands/gate-level-prompts.js +39 -23
- package/dist/cli/commands/gates.js +6 -7
- package/dist/cli/commands/init.js +150 -145
- package/dist/cli/commands/install-report.js +34 -0
- package/dist/cli/commands/legacy-sweep.js +64 -0
- package/dist/cli/commands/memory.js +59 -12
- package/dist/cli/commands/proxy.js +6 -1
- package/dist/cli/commands/rules.js +29 -12
- package/dist/cli/commands/skills.js +42 -9
- package/dist/cli/commands/tracker.js +9 -3
- package/dist/cli/commands/uninstall.js +354 -60
- package/dist/commands/bug-analysis.md +71 -122
- package/dist/commands/code-review.md +11 -11
- package/dist/commands/debug.md +2 -2
- package/dist/commands/dynamic-build.md +25 -25
- package/dist/commands/dynamic-plan.md +2 -2
- package/dist/commands/dynamic-profile.md +2 -2
- package/dist/commands/dynamic-tickets.md +2 -2
- package/dist/commands/explore.md +2 -2
- package/dist/commands/implement.md +48 -34
- package/dist/commands/plan.md +2 -2
- package/dist/commands/release.md +3 -3
- package/dist/commands/research.md +2 -2
- package/dist/commands/resolve.md +50 -46
- package/dist/commands/self-review.md +10 -9
- package/dist/compliance-off/agents/git.md +120 -0
- package/dist/compliance-off/agents/triage.md +180 -0
- package/dist/compliance-off/commands/bug-analysis.md +398 -0
- package/dist/compliance-off/commands/code-review.md +520 -0
- package/dist/compliance-off/commands/dynamic-build.md +1251 -0
- package/dist/compliance-off/commands/dynamic-tickets.md +676 -0
- package/dist/compliance-off/commands/implement.md +736 -0
- package/dist/compliance-off/commands/plan.md +671 -0
- package/dist/compliance-off/commands/release.md +309 -0
- package/dist/compliance-off/commands/resolve.md +742 -0
- package/dist/compliance-off/skills/git/references/decision-markers.md +17 -0
- package/dist/compliance-off/skills/git/references/learn-conventions.md +80 -0
- package/dist/compliance-off/skills/git/references/pr/check-ci-status.md +33 -0
- package/dist/compliance-off/skills/git/references/pr/check-merge-readiness.md +49 -0
- package/dist/compliance-off/skills/git/references/pr/fetch-review-threads.md +52 -0
- package/dist/compliance-off/skills/git/references/pr/validate-branch.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/_contract.md +33 -0
- package/dist/compliance-off/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/compliance-off/skills/git/references/tracker/github/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-traceable-issue.md +74 -0
- package/dist/compliance-off/skills/git/references/tracker/github/fetch-issue.md +69 -0
- package/dist/compliance-off/skills/git/references/tracker/github/fetch-issues-batch.md +65 -0
- package/dist/compliance-off/skills/git/references/tracker/github/gather-release-evidence.md +48 -0
- package/dist/compliance-off/skills/git/references/tracker/github/setup-task.md +77 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-traceable-issue.md +61 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issue.md +51 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issues-batch.md +63 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/gather-release-evidence.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/setup-task.md +82 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-traceable-issue.md +61 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issue.md +51 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issues-batch.md +63 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/gather-release-evidence.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/setup-task.md +83 -0
- package/dist/compliance-off/skills/git/references/trust-rule.md +7 -0
- package/dist/core/agent-models.js +19 -4
- package/dist/core/area-pause-notice.js +44 -0
- package/dist/core/assets.js +126 -17
- package/dist/core/compliance-off-files.js +27 -0
- package/dist/core/curation-move-skip.js +4 -5
- package/dist/core/curation-queue-cleanup.js +6 -5
- package/dist/core/curation-store.js +1 -0
- package/dist/core/curation-variants.js +460 -112
- package/dist/core/evidence-policy.js +10 -8
- package/dist/core/flags.js +2 -2
- package/dist/core/installed-variants.js +63 -0
- package/dist/core/manifest.js +48 -5
- package/dist/core/mds-variants.js +700 -3
- package/dist/core/orphan-sweep.js +11 -2
- package/dist/core/plugins.js +69 -12
- package/dist/core/tracked-areas.js +138 -14
- package/dist/curation-compliance-off/agents/triage.md +176 -0
- package/dist/curation-compliance-off/commands/bug-analysis.md +349 -0
- package/dist/curation-compliance-off/commands/code-review.md +481 -0
- package/dist/curation-compliance-off/commands/dynamic-build.md +1211 -0
- package/dist/curation-compliance-off/commands/dynamic-tickets.md +634 -0
- package/dist/curation-compliance-off/commands/implement.md +689 -0
- package/dist/curation-compliance-off/commands/plan.md +631 -0
- package/dist/curation-compliance-off/commands/release.md +279 -0
- package/dist/curation-compliance-off/commands/resolve.md +692 -0
- package/dist/curation-off/agents/code.md +10 -10
- package/dist/curation-off/agents/fix.md +7 -7
- package/dist/curation-off/agents/harden.md +7 -6
- package/dist/curation-off/agents/refactor.md +6 -6
- package/dist/curation-off/agents/triage.md +7 -0
- package/dist/curation-off/commands/bug-analysis.md +69 -119
- package/dist/curation-off/commands/code-review.md +11 -11
- package/dist/curation-off/commands/debug.md +2 -2
- package/dist/curation-off/commands/dynamic-build.md +25 -25
- package/dist/curation-off/commands/explore.md +2 -2
- package/dist/curation-off/commands/implement.md +48 -34
- package/dist/curation-off/commands/plan.md +2 -2
- package/dist/curation-off/commands/release.md +1 -1
- package/dist/curation-off/commands/resolve.md +50 -46
- package/dist/curation-off/commands/self-review.md +10 -9
- package/dist/hud/components/curation-counts.js +27 -0
- 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/resolve-review-threads.md +1 -1
- package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
- package/dist/skills/git/references/publication-gate.md +6 -7
- package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
- package/dist/targets/claude-code/behavior-install.js +23 -9
- package/dist/targets/claude-code/charter-render.js +374 -0
- package/dist/targets/claude-code/compliance-install.js +32 -12
- package/dist/targets/claude-code/compliance-off-files.js +245 -0
- package/dist/targets/claude-code/curation-install.js +570 -100
- package/dist/targets/claude-code/installer.js +467 -62
- package/dist/targets/claude-code/language-stamp.js +6 -0
- package/dist/targets/claude-code/linked-folder.js +182 -0
- package/dist/targets/claude-code/reference-converge.js +118 -0
- package/dist/targets/claude-code/tracker-install.js +21 -8
- package/dist/targets/claude-code/tracker-servers.js +20 -6
- package/dist/targets/claude-code/workflow-folder-install.js +231 -0
- package/dist/variant-axes.json +109 -0
- package/package.json +1 -1
- package/src/assets/agents/_partials/_code.mds +6 -6
- package/src/assets/agents/code.mds +4 -4
- package/src/assets/agents/evaluate.md +7 -13
- package/src/assets/agents/fix.mds +1 -1
- package/src/assets/agents/git.mds +34 -2
- package/src/assets/agents/harden.mds +7 -6
- package/src/assets/agents/skim.mds +1 -1
- package/src/assets/agents/test.md +21 -12
- package/src/assets/agents/triage.mds +9 -0
- package/src/assets/agents/validate.md +27 -34
- package/src/assets/commands/_partials/_compliance.mds +5 -3
- package/src/assets/commands/_partials/_decisions.mds +10 -8
- package/src/assets/commands/_partials/_engine.mds +9 -8
- package/src/assets/commands/_partials/_publication.mds +10 -1
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/bug-analysis.mds +80 -126
- package/src/assets/commands/code-review.mds +52 -9
- package/src/assets/commands/dynamic-build.mds +109 -15
- package/src/assets/commands/dynamic-tickets.mds +13 -0
- package/src/assets/commands/implement.mds +145 -33
- package/src/assets/commands/plan.mds +19 -0
- package/src/assets/commands/release.mds +50 -1
- package/src/assets/commands/resolve.mds +229 -44
- package/src/assets/commands/self-review.mds +8 -7
- package/src/assets/mds/git/_ops.mds +29 -1
- package/src/assets/mds/git/_ops_pr.mds +20 -10
- package/src/assets/mds/git/_ops_steps.mds +27 -0
- package/src/assets/mds/git/_pr.mds +15 -15
- package/src/assets/mds/git/_references.mds +12 -7
- package/src/assets/mds/tracker/_common.mds +4 -0
- package/src/assets/mds/tracker/_github.mds +43 -4
- package/src/assets/mds/tracker/_jira.mds +44 -4
- package/src/assets/mds/tracker/_linear.mds +44 -4
- package/src/assets/rules/quality.md +2 -0
- package/src/assets/scripts/ci-wait.cjs +493 -118
- package/src/assets/scripts/hooks/assets/implement-installed +1 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +13 -11
- package/src/assets/scripts/hooks/background-memory-update +79 -1
- package/src/assets/scripts/hooks/json-helper.cjs +44 -2
- package/src/assets/scripts/hooks/lib/curation-store.cjs +82 -20
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +9 -1
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +226 -33
- package/src/assets/scripts/hooks/memory-permission +10 -2
- package/src/assets/scripts/hooks/memory-worker +11 -0
- package/src/assets/scripts/hooks/pre-compact-memory +11 -0
- package/src/assets/scripts/hooks/preamble +18 -8
- package/src/assets/scripts/hooks/queue-append +218 -1
- package/src/assets/scripts/hooks/session-start-context +89 -37
- package/src/assets/scripts/hooks/session-start-memory +39 -0
- package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
- package/src/assets/scripts/pr-evidence.cjs +46 -4
- package/src/assets/scripts/resolve-settings.cjs +244 -30
- package/src/assets/scripts/verify-evidence.cjs +708 -124
- package/src/assets/skills/agent-references/SKILL.md +7 -1
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +6 -4
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +3 -3
- package/src/assets/skills/agent-references/references/commands/plan-levels.md +1 -1
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +3 -2
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
- 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/docs-framework/SKILL.md +5 -2
- package/src/assets/skills/git/SKILL.md +3 -3
- package/src/assets/skills/git/references/github-api.md +2 -139
- package/src/assets/skills/git/references/github-posting.md +158 -0
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +1 -1
- package/src/assets/skills/review-methodology/references/report-template.md +1 -2
- package/src/assets/skills/testing/SKILL.md +2 -4
|
@@ -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
|
|
@@ -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,
|
|
@@ -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/CURATION=
|
|
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,
|
|
@@ -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
|
-
curation: Object.freeze({ on:
|
|
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'),
|
|
@@ -705,7 +719,9 @@ function foldGateLevel(personal, manifest) {
|
|
|
705
719
|
* The whole-file rule's resolution (D-SETTINGS-LINE): the fail-closed values,
|
|
706
720
|
* naming the layer whose file could not be read, except the compliance lens —
|
|
707
721
|
* a broken file affects only the keys it owns, so the lens stays the fold of
|
|
708
|
-
* 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
|
|
709
725
|
* nothing readable of its own (repoCompliance null) but reads as a malformed
|
|
710
726
|
* declaration, so its lens is at least `generic`; an unreadable config.json
|
|
711
727
|
* owns no compliance at all, so the worktree project.json's ids still count.
|
|
@@ -717,8 +733,11 @@ function foldGateLevel(personal, manifest) {
|
|
|
717
733
|
function unreadableSettings(layer, inputs) {
|
|
718
734
|
const defaultBranch = inputs.defaultBranch === undefined ? ABSENT_FIELD : inputs.defaultBranch;
|
|
719
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' });
|
|
720
738
|
return Object.freeze({
|
|
721
739
|
...FAIL_CLOSED_SETTINGS,
|
|
740
|
+
switches: Object.freeze({ ...FAIL_CLOSED_SETTINGS.switches, memory: machine('memory'), curation: machine('curation') }),
|
|
722
741
|
// The level stays `full` with no deciding layer, but a readable manifest still shows its own value.
|
|
723
742
|
gateLevelMachine: machineGateLevel(inputs.manifest),
|
|
724
743
|
compliance: lens.compliance,
|
|
@@ -758,6 +777,19 @@ function foldSettings(inputs) {
|
|
|
758
777
|
});
|
|
759
778
|
}
|
|
760
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
|
+
|
|
761
793
|
// ---------------------------------------------------------------------------
|
|
762
794
|
// Gathering the layers (the imperative shell — D-SETTINGS-LOCAL-ONLY)
|
|
763
795
|
// ---------------------------------------------------------------------------
|
|
@@ -773,18 +805,20 @@ function defaultExec(file, args, opts) {
|
|
|
773
805
|
|
|
774
806
|
/**
|
|
775
807
|
* One bounded local git call, normalized. The argv is a fresh copy, so an exec
|
|
776
|
-
* 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.
|
|
777
810
|
*
|
|
778
811
|
* @param {ExecFn} exec
|
|
779
812
|
* @param {string} cwd
|
|
780
813
|
* @param {readonly string[]} args
|
|
781
814
|
* @param {number} maxBuffer
|
|
815
|
+
* @param {NodeJS.ProcessEnv} [baseEnv]
|
|
782
816
|
* @returns {GitAnswer}
|
|
783
817
|
*/
|
|
784
|
-
function runGit(exec, cwd, args, maxBuffer) {
|
|
818
|
+
function runGit(exec, cwd, args, maxBuffer, baseEnv) {
|
|
785
819
|
const res = exec('git', [...args], {
|
|
786
820
|
cwd,
|
|
787
|
-
env: Object.assign({}, process.env, { GIT_TERMINAL_PROMPT: '0' }),
|
|
821
|
+
env: Object.assign({}, baseEnv === undefined ? process.env : baseEnv, { GIT_TERMINAL_PROMPT: '0' }),
|
|
788
822
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
789
823
|
timeout: GIT_TIMEOUT_MS,
|
|
790
824
|
maxBuffer,
|
|
@@ -849,7 +883,8 @@ function gitToplevel(exec, dir) {
|
|
|
849
883
|
* so it is unreadable and its keys fail closed
|
|
850
884
|
* `--no-literal-pathspecs` leads the argv: an inherited GIT_LITERAL_PATHSPECS=1
|
|
851
885
|
* would read every pathspec as a plain path, magic included, list nothing and
|
|
852
|
-
* 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.
|
|
853
888
|
*
|
|
854
889
|
* @param {ExecFn} exec
|
|
855
890
|
* @param {string} root
|
|
@@ -858,7 +893,8 @@ function gitToplevel(exec, dir) {
|
|
|
858
893
|
*/
|
|
859
894
|
function personalTracking(exec, root, rootConfirmed) {
|
|
860
895
|
const res = runGit(exec, root,
|
|
861
|
-
['--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));
|
|
862
898
|
if (!res.answered) return 'unknown';
|
|
863
899
|
if (res.stdout.length > 0) return 'tracked';
|
|
864
900
|
if (res.ok) return 'untracked';
|
|
@@ -953,13 +989,180 @@ function defaultBranchCompliance(exec, root) {
|
|
|
953
989
|
return fieldIn(projectConfig.parseProjectBytes(blob.stdout), 'compliance', 'malformed');
|
|
954
990
|
}
|
|
955
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
|
+
|
|
956
1151
|
/**
|
|
957
1152
|
* Resolve the settings for `opts.dir`. Never throws: a git that cannot say
|
|
958
1153
|
* whether `opts.dir` is in a repository, or any internal failure, is the
|
|
959
1154
|
* fail-closed resolution, which main() maps to exit 4. A repository file that
|
|
960
|
-
* exists but is unreadable fails every field closed but the compliance lens
|
|
961
|
-
* naming its layer in `unreadable` (the
|
|
962
|
-
* 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.
|
|
963
1166
|
*
|
|
964
1167
|
* @param {ResolveSettingsOptions} opts
|
|
965
1168
|
* @param {{ exec?: ExecFn }} [deps]
|
|
@@ -973,7 +1176,12 @@ function resolveSettings(opts, deps) {
|
|
|
973
1176
|
if (toplevel.kind === 'unknown') return FAIL_CLOSED_SETTINGS;
|
|
974
1177
|
if (toplevel.kind === 'none') {
|
|
975
1178
|
const none = projectConfig.parseProjectBytes(null);
|
|
976
|
-
|
|
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
|
+
}));
|
|
977
1185
|
}
|
|
978
1186
|
const layers = readRepoLayers(toplevel.root, { exec, rootConfirmed: true });
|
|
979
1187
|
/** @type {SettingsInputs} */
|
|
@@ -985,9 +1193,14 @@ function resolveSettings(opts, deps) {
|
|
|
985
1193
|
defaultBranch: defaultBranchCompliance(exec, toplevel.root),
|
|
986
1194
|
retiredPolicyFile: existsNoFollow(path.join(toplevel.root, '.devflow', 'policy.json')),
|
|
987
1195
|
};
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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));
|
|
991
1204
|
} catch (_) {
|
|
992
1205
|
return FAIL_CLOSED_SETTINGS;
|
|
993
1206
|
}
|
|
@@ -1238,6 +1451,7 @@ module.exports = Object.freeze({
|
|
|
1238
1451
|
foldSettings,
|
|
1239
1452
|
readRepoLayers,
|
|
1240
1453
|
machineSwitchOn,
|
|
1454
|
+
ledgerRootOf,
|
|
1241
1455
|
resolveSettings,
|
|
1242
1456
|
formatSettingsLine,
|
|
1243
1457
|
isCoherentSettingsLine,
|