devflow-kit 2.4.0 → 2.5.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 +156 -0
- package/README.md +86 -18
- package/dist/agents/git.md +824 -0
- package/dist/cli/commands/agents.js +6 -1
- package/dist/cli/commands/attribution-prompts.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +1 -1
- package/dist/cli/commands/compliance.js +23 -1
- package/dist/cli/commands/init-seed.js +24 -26
- package/dist/cli/commands/init.js +502 -71
- package/dist/cli/commands/install-report.js +205 -0
- package/dist/cli/commands/knowledge/index.js +2 -2
- package/dist/cli/commands/knowledge/toggle.js +27 -37
- package/dist/cli/commands/learning.js +37 -30
- package/dist/cli/commands/memory.js +79 -69
- package/dist/cli/commands/prompt-io.js +4 -4
- package/dist/cli/commands/security.js +76 -16
- package/dist/cli/commands/skills.js +53 -7
- package/dist/cli/commands/tracker-prompts.js +145 -0
- package/dist/cli/commands/tracker.js +405 -0
- package/dist/cli/commands/uninstall.js +211 -65
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +22 -4
- package/dist/commands/code-review.md +44 -15
- package/dist/commands/debug.md +20 -6
- package/dist/commands/dynamic-build.md +289 -67
- package/dist/commands/dynamic-plan.md +60 -21
- package/dist/commands/dynamic-profile.md +1 -1
- package/dist/commands/dynamic-tickets.md +58 -8
- package/dist/commands/explore.md +2 -2
- package/dist/commands/implement.md +241 -53
- package/dist/commands/plan.md +88 -17
- package/dist/commands/release.md +64 -17
- package/dist/commands/resolve.md +138 -58
- package/dist/commands/self-review.md +2 -2
- package/dist/core/agent-models.js +55 -12
- package/dist/core/assets.js +58 -2
- package/dist/core/evidence-policy.js +147 -0
- package/dist/core/feature-config.js +130 -64
- package/dist/core/feature-switch.js +112 -0
- package/dist/core/flags.js +4 -4
- package/dist/core/manifest.js +33 -7
- package/dist/core/mds-variants.js +861 -0
- package/dist/core/model-discovery.js +12 -1
- package/dist/core/plugins.js +357 -9
- package/dist/core/project-paths.js +1 -1
- package/dist/core/proxy-log.js +8 -6
- package/dist/core/proxy-state.js +11 -8
- package/dist/core/reference-sweep.js +136 -0
- package/dist/core/tracker.js +407 -0
- package/dist/skills/git/references/decision-markers.md +19 -0
- package/dist/skills/git/references/learn-conventions.md +56 -0
- package/dist/skills/git/references/pr/check-ci-status.md +14 -0
- package/dist/skills/git/references/pr/check-merge-readiness.md +28 -0
- package/dist/skills/git/references/pr/ensure-pr-ready.md +24 -0
- package/dist/skills/git/references/pr/fetch-review-threads.md +22 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +40 -0
- package/dist/skills/git/references/pr/post-review-summary.md +42 -0
- package/dist/skills/git/references/pr/resolve-review-threads.md +35 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +14 -0
- package/dist/skills/git/references/pr/validate-branch.md +18 -0
- package/dist/skills/git/references/publication-gate.md +13 -0
- package/dist/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/skills/git/references/tracker/github/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +40 -0
- package/dist/skills/git/references/tracker/github/create-release.md +11 -0
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +16 -0
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +69 -0
- package/dist/skills/git/references/tracker/github/fetch-issue.md +32 -0
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +17 -0
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +19 -0
- package/dist/skills/git/references/tracker/github/manage-debt.md +101 -0
- package/dist/skills/git/references/tracker/github/post-wave-report.md +28 -0
- package/dist/skills/git/references/tracker/github/setup-task.md +26 -0
- package/dist/skills/git/references/tracker/jira/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +49 -0
- package/dist/skills/git/references/tracker/jira/create-release.md +17 -0
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/jira/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/jira/setup-task.md +31 -0
- package/dist/skills/git/references/tracker/linear/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +53 -0
- package/dist/skills/git/references/tracker/linear/create-release.md +17 -0
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/linear/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/linear/setup-task.md +32 -0
- package/dist/skills/git/references/trust-rule.md +7 -0
- package/dist/targets/claude-code/installer.js +1213 -31
- package/dist/targets/claude-code/legacy.js +5 -0
- package/dist/targets/claude-code/post-install.js +196 -74
- package/dist/targets/claude-code/tracker-install.js +161 -0
- package/package.json +4 -3
- package/src/assets/agents/code.md +42 -4
- package/src/assets/agents/design.md +1 -1
- package/src/assets/agents/git.mds +827 -0
- package/src/assets/agents/knowledge.md +1 -1
- package/src/assets/agents/learning.md +11 -0
- package/src/assets/agents/synthesize.md +1 -1
- package/src/assets/agents/test.md +16 -5
- package/src/assets/agents/tracker.md +467 -0
- package/src/assets/agents/validate.md +7 -5
- package/src/assets/commands/_partials/_engine.mds +11 -9
- package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
- package/src/assets/commands/_partials/_knowledge.mds +2 -2
- package/src/assets/commands/_partials/_plan_contract.mds +22 -7
- package/src/assets/commands/_partials/_preamble.mds +1 -1
- package/src/assets/commands/_partials/_publication.mds +3 -1
- package/src/assets/commands/_partials/_ticket_template.mds +3 -2
- package/src/assets/commands/_partials/_tracker.mds +18 -0
- package/src/assets/commands/_partials/_wave.mds +16 -10
- package/src/assets/commands/bug-analysis.mds +15 -5
- package/src/assets/commands/code-review.mds +34 -14
- package/src/assets/commands/debug.mds +11 -4
- package/src/assets/commands/dynamic-build.mds +227 -41
- package/src/assets/commands/dynamic-plan.mds +35 -13
- package/src/assets/commands/dynamic-tickets.mds +47 -5
- package/src/assets/commands/implement.mds +206 -52
- package/src/assets/commands/plan.mds +70 -17
- package/src/assets/commands/release.md +64 -17
- package/src/assets/commands/resolve.mds +126 -56
- package/src/assets/mds/git/_pr.mds +331 -0
- package/src/assets/mds/git/_references.mds +135 -0
- package/src/assets/mds/tracker/_common.mds +156 -0
- package/src/assets/mds/tracker/_github.mds +472 -0
- package/src/assets/mds/tracker/_jira.mds +407 -0
- package/src/assets/mds/tracker/_linear.mds +449 -0
- package/src/assets/mds/tracker/_mcp.mds +299 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
- package/src/assets/scripts/hooks/background-memory-update +14 -9
- package/src/assets/scripts/hooks/capture-prompt +6 -2
- package/src/assets/scripts/hooks/capture-question +6 -2
- package/src/assets/scripts/hooks/capture-turn +6 -2
- package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
- package/src/assets/scripts/hooks/ensure-root-gitignore +161 -60
- package/src/assets/scripts/hooks/hook-log-init +3 -1
- package/src/assets/scripts/hooks/json-helper.cjs +223 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -1
- package/src/assets/scripts/hooks/memory-worker +15 -8
- package/src/assets/scripts/hooks/pre-compact-memory +12 -8
- package/src/assets/scripts/hooks/preamble +1 -4
- package/src/assets/scripts/hooks/queue-append +68 -24
- package/src/assets/scripts/hooks/session-start-context +355 -8
- package/src/assets/scripts/hooks/session-start-memory +12 -8
- package/src/assets/scripts/pr-evidence.cjs +1961 -0
- package/src/assets/scripts/redact-secrets.cjs +490 -62
- package/src/assets/scripts/release-trace.cjs +1143 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +1065 -0
- package/src/assets/scripts/verify-evidence.cjs +1822 -0
- package/src/assets/skills/compliance/SKILL.md +2 -0
- package/src/assets/skills/docs-framework/SKILL.md +5 -3
- package/src/assets/skills/git/SKILL.md +8 -78
- package/src/assets/skills/git/references/github-api.md +179 -141
- package/src/assets/skills/git/references/patterns.md +11 -6
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +6 -61
- package/src/assets/skills/review-methodology/references/violations.md +14 -22
- package/src/assets/agents/git.md +0 -938
package/dist/core/assets.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { getPackageRoot } from './paths.js';
|
|
3
|
+
import { SKILL_REFS_OUTPUT_DIR } from './mds-variants.js';
|
|
3
4
|
/**
|
|
4
5
|
* Flat skills source directory: src/assets/skills/{name}/
|
|
5
6
|
* All plugins' skills live here directly (no per-plugin subdirectory).
|
|
@@ -10,9 +11,13 @@ export function skillsDir() {
|
|
|
10
11
|
/**
|
|
11
12
|
* Flat agents source directory: src/assets/agents/{name}.md
|
|
12
13
|
* All plugins' agents live here directly.
|
|
14
|
+
*
|
|
15
|
+
* @param root - Package root to resolve against. Injectable so a caller working
|
|
16
|
+
* on a temp tree (the test harness) reads the layout from here rather than
|
|
17
|
+
* spelling the path itself.
|
|
13
18
|
*/
|
|
14
|
-
export function agentsDir() {
|
|
15
|
-
return join(
|
|
19
|
+
export function agentsDir(root = getPackageRoot()) {
|
|
20
|
+
return join(root, 'src', 'assets', 'agents');
|
|
16
21
|
}
|
|
17
22
|
/**
|
|
18
23
|
* Flat rules source directory: src/assets/rules/{name}.md
|
|
@@ -36,4 +41,55 @@ export function scriptsDir() {
|
|
|
36
41
|
export function commandsDir() {
|
|
37
42
|
return join(getPackageRoot(), 'dist', 'commands');
|
|
38
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Compiled agents directory: dist/agents/{name}.md
|
|
46
|
+
*
|
|
47
|
+
* Output of the .mds generator hosts. The directory is absent until at least
|
|
48
|
+
* one generator host exists, so every reader must tolerate its absence.
|
|
49
|
+
*
|
|
50
|
+
* @param root - Package root to resolve against (see agentsDir).
|
|
51
|
+
*/
|
|
52
|
+
export function compiledAgentsDir(root = getPackageRoot()) {
|
|
53
|
+
return join(root, 'dist', 'agents');
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Compiled skill-reference directory: dist/skills/git/references/
|
|
57
|
+
*
|
|
58
|
+
* Output of the `.mds` reference modules — the generated `devflow:git` mechanics
|
|
59
|
+
* files, one per (provider, operation) pair under `tracker/{provider}/`. Like
|
|
60
|
+
* compiledAgentsDir(), the directory is absent until the build has run, so every
|
|
61
|
+
* reader must tolerate its absence.
|
|
62
|
+
*
|
|
63
|
+
* The spelling comes from SKILL_REFS_OUTPUT_DIR in src/core/mds-variants.ts —
|
|
64
|
+
* the build's own allowlist table — rather than being retyped here, so the
|
|
65
|
+
* destination has exactly one definition.
|
|
66
|
+
*
|
|
67
|
+
* @param root - Package root to resolve against (see agentsDir).
|
|
68
|
+
*/
|
|
69
|
+
export function compiledSkillRefsDir(root = getPackageRoot()) {
|
|
70
|
+
return join(root, ...SKILL_REFS_OUTPUT_DIR.split('/'));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Agent source directories, MOST-PREFERRED FIRST.
|
|
74
|
+
*
|
|
75
|
+
* The single owner of the dist-first agent-resolution policy: a generator
|
|
76
|
+
* host's compiled artifact in dist/agents/ supersedes a hand-authored file of
|
|
77
|
+
* the same name in src/assets/agents/. Every consumer reads the order from
|
|
78
|
+
* here — the installer's first-hit-wins resolve, loadShippedDefaults's
|
|
79
|
+
* first-wins merge, and the test harness's resolveAgentSource — so the
|
|
80
|
+
* convention is stated once and cannot drift apart between call sites.
|
|
81
|
+
*
|
|
82
|
+
* Order is invisible to the type system: a list spelled least-preferred-first
|
|
83
|
+
* still typechecks and silently inverts the answer. Consumers therefore take
|
|
84
|
+
* this list as-is and never re-spell it; tests/guards/agent-source-precedence
|
|
85
|
+
* pins that they agree.
|
|
86
|
+
*
|
|
87
|
+
* The non-empty tuple makes an empty list a compile error at every call site:
|
|
88
|
+
* an empty list would survive a `??` default and resolve to nothing.
|
|
89
|
+
*
|
|
90
|
+
* @param root - Package root to resolve against (see agentsDir).
|
|
91
|
+
*/
|
|
92
|
+
export function agentSourceDirs(root = getPackageRoot()) {
|
|
93
|
+
return [compiledAgentsDir(root), agentsDir(root)];
|
|
94
|
+
}
|
|
39
95
|
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's view of the evidence policy — a typed seam onto the package's own
|
|
3
|
+
* `resolve-evidence-policy.cjs`, never a second implementation of it.
|
|
4
|
+
*
|
|
5
|
+
* D-POLICY-CJS-SEAM: the resolver is plain CommonJS under src/assets/scripts/,
|
|
6
|
+
* outside every tsconfig (PF-043, PF-069), so the interfaces below are
|
|
7
|
+
* TRANSCRIBED from its JSDoc typedefs and are the only shape authority on this
|
|
8
|
+
* side — open those typedefs before changing anything here. The module is loaded
|
|
9
|
+
* with `require()` from `scriptsDir()`, which resolves under the package root both
|
|
10
|
+
* from `dist/cli.js` and under vitest (`package.json` `files` ships src/assets/),
|
|
11
|
+
* so the CLI and the resolver it runs are always the same version. The installed
|
|
12
|
+
* `~/.devflow/scripts` copy is never loaded: it may be older than this CLI. There
|
|
13
|
+
* is deliberately no TypeScript copy of the parser, the fold or the grammar.
|
|
14
|
+
*
|
|
15
|
+
* D-POLICY-NO-WRITE (applies ADR-024): `.devflow/policy.json` is team-owned, and
|
|
16
|
+
* devflow never writes or replaces a shared file it cannot prove it wrote. This
|
|
17
|
+
* module therefore imports no fs API; the CLI only PRINTS the bytes a team may
|
|
18
|
+
* choose to commit (`evidencePolicySuggestion`).
|
|
19
|
+
*/
|
|
20
|
+
import { createRequire } from 'module';
|
|
21
|
+
import { join } from 'path';
|
|
22
|
+
import { scriptsDir } from './assets.js';
|
|
23
|
+
// ── Transcribed shapes (resolve-evidence-policy.cjs JSDoc) ─────────────────────
|
|
24
|
+
/** Basename of the resolver under src/assets/scripts/ (and ~/.devflow/scripts/). */
|
|
25
|
+
export const RESOLVER_SCRIPT_NAME = 'resolve-evidence-policy.cjs';
|
|
26
|
+
/** The team file the CLI suggests committing, relative to a repository root. */
|
|
27
|
+
const POLICY_FILE = '.devflow/policy.json';
|
|
28
|
+
/**
|
|
29
|
+
* Every key of EvidencePolicyModule and the runtime kind the loader requires of
|
|
30
|
+
* it. `satisfies` makes the compiler reject an interface key missing here.
|
|
31
|
+
*/
|
|
32
|
+
export const EVIDENCE_POLICY_MODULE_SURFACE = Object.freeze({
|
|
33
|
+
POLICIES: 'string-array',
|
|
34
|
+
SOURCES: 'string-array',
|
|
35
|
+
WARNINGS: 'string-array',
|
|
36
|
+
MECHANISM_INPUTS: 'object',
|
|
37
|
+
OUTPUT_LINE_RE: 'regexp',
|
|
38
|
+
FAIL_CLOSED_LINE: 'string',
|
|
39
|
+
complianceDefault: 'function',
|
|
40
|
+
resolve: 'function',
|
|
41
|
+
serializePolicy: 'function',
|
|
42
|
+
});
|
|
43
|
+
function hasKind(value, kind) {
|
|
44
|
+
switch (kind) {
|
|
45
|
+
case 'string-array': return Array.isArray(value) && value.every(v => typeof v === 'string');
|
|
46
|
+
case 'object': return typeof value === 'object' && value !== null;
|
|
47
|
+
case 'regexp': return value instanceof RegExp;
|
|
48
|
+
case 'string': return typeof value === 'string';
|
|
49
|
+
case 'function': return typeof value === 'function';
|
|
50
|
+
default: {
|
|
51
|
+
const exhaustive = kind;
|
|
52
|
+
return exhaustive;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Surface keys that are absent or of the wrong kind on `value`, in surface order. */
|
|
57
|
+
function surfaceMismatches(value) {
|
|
58
|
+
if (typeof value !== 'object' || value === null)
|
|
59
|
+
return Object.keys(EVIDENCE_POLICY_MODULE_SURFACE);
|
|
60
|
+
const record = value;
|
|
61
|
+
return Object.entries(EVIDENCE_POLICY_MODULE_SURFACE)
|
|
62
|
+
.filter(([key, kind]) => !hasKind(record[key], kind))
|
|
63
|
+
.map(([key]) => key);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Load the resolver from `dir` (default: the package's own scripts directory) and
|
|
67
|
+
* shape-check its surface. Never throws: a missing file is `not-found`; a module
|
|
68
|
+
* that throws on load or lacks a surface key is `unusable`.
|
|
69
|
+
*/
|
|
70
|
+
export function loadEvidencePolicyModule(dir = scriptsDir()) {
|
|
71
|
+
const file = join(dir, RESOLVER_SCRIPT_NAME);
|
|
72
|
+
let loaded;
|
|
73
|
+
try {
|
|
74
|
+
loaded = createRequire(import.meta.url)(file);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const code = err.code;
|
|
78
|
+
if (code === 'MODULE_NOT_FOUND')
|
|
79
|
+
return { ok: false, error: { kind: 'not-found', path: file } };
|
|
80
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
81
|
+
return { ok: false, error: { kind: 'unusable', path: file, detail } };
|
|
82
|
+
}
|
|
83
|
+
const mismatches = surfaceMismatches(loaded);
|
|
84
|
+
if (mismatches.length > 0) {
|
|
85
|
+
return { ok: false, error: { kind: 'unusable', path: file, detail: `missing or mistyped: ${mismatches.join(', ')}` } };
|
|
86
|
+
}
|
|
87
|
+
return { ok: true, value: loaded };
|
|
88
|
+
}
|
|
89
|
+
// ── Presentation (pure) ────────────────────────────────────────────────────────
|
|
90
|
+
/** `Evidence policy: <policy> (source: <source>)`, plus ` [warn: a, b]` when warnings exist. */
|
|
91
|
+
export function formatEvidencePolicyStatus(r) {
|
|
92
|
+
const warn = r.warnings.length > 0 ? ` [warn: ${r.warnings.join(', ')}]` : '';
|
|
93
|
+
return `Evidence policy: ${r.policy} (source: ${r.source})${warn}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The line shown in place of a policy when the resolver cannot be loaded. The
|
|
97
|
+
* remedy is a package reinstall: the CLI loads the package's own copy, which
|
|
98
|
+
* `devflow init` does not restore.
|
|
99
|
+
*/
|
|
100
|
+
export function formatEvidencePolicyUnavailable(error) {
|
|
101
|
+
switch (error.kind) {
|
|
102
|
+
case 'not-found': return 'Evidence policy: unavailable (resolver not found — reinstall devflow-kit)';
|
|
103
|
+
case 'unusable': return 'Evidence policy: unavailable (resolver failed to load — reinstall devflow-kit)';
|
|
104
|
+
default: {
|
|
105
|
+
const exhaustive = error;
|
|
106
|
+
return exhaustive;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The `compliance --status` line: the resolved policy for `opts.dir`, or the
|
|
112
|
+
* unavailable line when the loader failed — that line is the whole handling
|
|
113
|
+
* (ADR-028). The caller passes the compliance state it already read, so the
|
|
114
|
+
* manifest is never read twice. `resolve()` makes at most two `gh` calls and
|
|
115
|
+
* bounds every subprocess with a timeout, so an offline machine degrades to a
|
|
116
|
+
* flagged result rather than a hang.
|
|
117
|
+
*/
|
|
118
|
+
export function evidencePolicyStatusLine(loaded, opts) {
|
|
119
|
+
if (!loaded.ok)
|
|
120
|
+
return formatEvidencePolicyUnavailable(loaded.error);
|
|
121
|
+
return formatEvidencePolicyStatus(loaded.value.resolve(opts));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* What `--enable`/`--set` print when compliance is on: the file a team may commit
|
|
125
|
+
* to pin the policy it now gets by default. Returned only when the resolver's own
|
|
126
|
+
* `complianceDefault` says `required` (compliance enabled, at any framework
|
|
127
|
+
* count); `null` otherwise. The bytes come from the resolver's `serializePolicy`,
|
|
128
|
+
* so they always parse as a valid policy file. Nothing is written
|
|
129
|
+
* (D-POLICY-NO-WRITE).
|
|
130
|
+
*/
|
|
131
|
+
export function evidencePolicySuggestion(complianceState, mod) {
|
|
132
|
+
if (mod.complianceDefault(complianceState) !== 'required')
|
|
133
|
+
return null;
|
|
134
|
+
const body = mod.serializePolicy('required');
|
|
135
|
+
if (body === null)
|
|
136
|
+
return null;
|
|
137
|
+
return [
|
|
138
|
+
'Compliance is enabled on this machine, so repositories without a committed',
|
|
139
|
+
'policy default to the required evidence policy here. To apply it for everyone',
|
|
140
|
+
`working in a repository, commit this as ${POLICY_FILE} on its default branch:`,
|
|
141
|
+
'',
|
|
142
|
+
`${body}`,
|
|
143
|
+
'devflow never writes this file: the team owns it, and once committed it applies',
|
|
144
|
+
'repo-wide.',
|
|
145
|
+
].join('\n');
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=evidence-policy.js.map
|
|
@@ -1,125 +1,191 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import { getFeatureConfigPath } from './project-paths.js';
|
|
4
|
+
import { parseTrackerId } from './tracker.js';
|
|
5
|
+
/**
|
|
6
|
+
* Keys devflow itself once wrote and has retired. A managed write drops them
|
|
7
|
+
* rather than carrying them; no reader consults them.
|
|
8
|
+
*
|
|
9
|
+
* D-FEATURES-MACHINE-WIDE: `memory`, `learning` and `knowledge` were per-repo
|
|
10
|
+
* feature switches from the per-repo-install era. A feature is now on or off
|
|
11
|
+
* for the whole machine, in the manifest, so a stale per-repo value must
|
|
12
|
+
* neither decide anything nor linger to be mistaken for a switch: carrying it
|
|
13
|
+
* would leave a `learning: false` in the file that no longer does what it says.
|
|
14
|
+
* `decisions` is the pre-rename spelling of `learning`; `autoCommit` is inert.
|
|
15
|
+
*/
|
|
16
|
+
const RETIRED_CONFIG_KEYS = new Set([
|
|
17
|
+
'memory', 'learning', 'knowledge', 'decisions', 'autoCommit',
|
|
18
|
+
]);
|
|
4
19
|
export const DEFAULT_CONFIG = {
|
|
5
|
-
memory: true,
|
|
6
|
-
learning: true,
|
|
7
|
-
knowledge: true,
|
|
8
20
|
reviewPublication: 'auto',
|
|
9
21
|
};
|
|
10
22
|
export function getConfigPath(projectRoot) {
|
|
11
23
|
return getFeatureConfigPath(projectRoot);
|
|
12
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse the per-repo tracker override from the raw config value.
|
|
27
|
+
*
|
|
28
|
+
* Pure. Delegates membership to `parseTrackerId` rather than re-spelling a
|
|
29
|
+
* closed-domain ternary the way `reviewPublication` does: `reviewPublication`
|
|
30
|
+
* self-heals any invalid value to `'auto'`, which is correct for a publication
|
|
31
|
+
* mode and wrong for a provider — a repaired provider is the laundering path
|
|
32
|
+
* GAP-10 names, and §14.2 gives the invalid case its own DEGRADED reason, which
|
|
33
|
+
* only exists if the parse REFUSES instead of healing.
|
|
34
|
+
*
|
|
35
|
+
* An empty string reads as absent: `"tracker": ""` is an unset key with a
|
|
36
|
+
* character in it, not an attempt to name a provider.
|
|
37
|
+
*
|
|
38
|
+
* A non-string JSON value (`42`, `true`, `null`, an array, an object) is
|
|
39
|
+
* `invalid`, not `absent` — a present-but-wrong-typed value means the file was
|
|
40
|
+
* edited, and reporting it as absent would make the whole class silent.
|
|
41
|
+
*/
|
|
42
|
+
export function parseTrackerOverride(raw) {
|
|
43
|
+
if (raw === undefined || raw === '')
|
|
44
|
+
return { kind: 'absent' };
|
|
45
|
+
// `unknown` all the way from the field: this is a boundary parse over
|
|
46
|
+
// hand-edited JSON, and a signature that promised a string would make the
|
|
47
|
+
// wrong-type arm unreachable to the compiler while it stays entirely reachable
|
|
48
|
+
// to a user with a text editor.
|
|
49
|
+
if (typeof raw !== 'string') {
|
|
50
|
+
return { kind: 'invalid', raw: JSON.stringify(raw) ?? String(raw) };
|
|
51
|
+
}
|
|
52
|
+
const parsed = parseTrackerId(raw);
|
|
53
|
+
return parsed.ok ? { kind: 'valid', provider: parsed.value } : { kind: 'invalid', raw };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether a parsed JSON value is an object a config can be read from — the one
|
|
57
|
+
* test for "the file holds a config", shared by the reader and the managed
|
|
58
|
+
* write so the two never disagree about which files count as empty.
|
|
59
|
+
*/
|
|
60
|
+
function isJsonObject(value) {
|
|
61
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
62
|
+
}
|
|
13
63
|
/**
|
|
14
64
|
* Parse and narrow an unknown JSON value into a FeatureConfig, merging onto
|
|
15
65
|
* DEFAULT_CONFIG. Pure function — no I/O, no side effects.
|
|
16
66
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* manifest.ts's new-key-wins self-heal — migration-compat requires the old key
|
|
20
|
-
* to take precedence so old configs with `decisions: false` are not silently
|
|
21
|
-
* re-enabled by a newer `learning: true` key).
|
|
22
|
-
* Silently ignores `autoCommit` — old configs may still contain it.
|
|
67
|
+
* The retired keys ({@link RETIRED_CONFIG_KEYS}) are ignored: an old config may
|
|
68
|
+
* still hold them, and none of them decides anything (D-FEATURES-MACHINE-WIDE).
|
|
23
69
|
*
|
|
24
70
|
* Returns null when `parsed` is not a plain object (caller falls through to
|
|
25
71
|
* the next candidate path).
|
|
26
72
|
*/
|
|
27
73
|
function coerceConfig(parsed) {
|
|
28
|
-
if (
|
|
74
|
+
if (!isJsonObject(parsed))
|
|
29
75
|
return null;
|
|
30
76
|
const p = parsed;
|
|
31
|
-
// Coalesce decisions (legacy key) → learning. decisions wins when both present.
|
|
32
|
-
let learning = DEFAULT_CONFIG.learning;
|
|
33
|
-
if (typeof p.learning === 'boolean')
|
|
34
|
-
learning = p.learning;
|
|
35
|
-
if (typeof p.decisions === 'boolean')
|
|
36
|
-
learning = p.decisions; // decisions wins
|
|
37
77
|
// Coerce reviewPublication: any invalid or absent value → 'auto' (self-heal, ADR-014 idiom).
|
|
38
78
|
const rp = p.reviewPublication;
|
|
39
79
|
const reviewPublication = rp === 'auto' || rp === 'full' || rp === 'off' ? rp : 'auto';
|
|
80
|
+
// The per-repo tracker override is carried through VERBATIM — never coerced,
|
|
81
|
+
// never type-filtered. Two reasons, and the second is the one a reader is
|
|
82
|
+
// likely to miss:
|
|
83
|
+
// (a) repair is forbidden for a provider value (§14.9-6), so there is no
|
|
84
|
+
// healed value to fall back to the way reviewPublication has 'auto';
|
|
85
|
+
// (b) a non-string is a state parseTrackerOverride CLASSIFIES (`invalid`),
|
|
86
|
+
// not a state this function repairs. Filtering by type here would leave
|
|
87
|
+
// that arm reachable from a direct call to the parser and unreachable
|
|
88
|
+
// from the file, which is the only place it can actually be written.
|
|
89
|
+
// Key PRESENCE is the whole rule: a present key is carried as written, and an
|
|
90
|
+
// absent one stays absent. `hasOwnProperty` rather than `in` because the
|
|
91
|
+
// object comes from JSON.parse at a trust boundary. Retention on disk is not
|
|
92
|
+
// decided here: the writers carry every unmanaged key from the file itself
|
|
93
|
+
// (D-CONFIG-PRESERVE-UNMANAGED).
|
|
94
|
+
const hasTracker = Object.prototype.hasOwnProperty.call(p, 'tracker');
|
|
40
95
|
return {
|
|
41
|
-
memory: typeof p.memory === 'boolean' ? p.memory : DEFAULT_CONFIG.memory,
|
|
42
|
-
learning,
|
|
43
|
-
knowledge: typeof p.knowledge === 'boolean' ? p.knowledge : DEFAULT_CONFIG.knowledge,
|
|
44
96
|
reviewPublication,
|
|
97
|
+
...(hasTracker ? { tracker: p.tracker } : {}),
|
|
45
98
|
};
|
|
46
99
|
}
|
|
47
100
|
/**
|
|
48
|
-
* Read the
|
|
101
|
+
* Read the per-repo config for a project root.
|
|
49
102
|
* Returns DEFAULT_CONFIG when the file is missing or unreadable.
|
|
50
|
-
* Applies ADR-001: .devflow/config.json is the sole source of truth;
|
|
51
|
-
* `devflow init` writes it directly on first install. An absent file falls
|
|
52
|
-
* through to DEFAULT_CONFIG (all features enabled by default).
|
|
53
103
|
*/
|
|
54
104
|
export async function readConfig(projectRoot) {
|
|
55
|
-
|
|
105
|
+
return coerceConfig(await readConfigBody(projectRoot)) ?? { ...DEFAULT_CONFIG };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The parsed JSON body of a project's config file, or `undefined` when the file
|
|
109
|
+
* is absent, unreadable or malformed. Every read of the file goes through here,
|
|
110
|
+
* so readConfig, readConfigIfPresent and writeManagedConfig agree on what an
|
|
111
|
+
* unusable file means.
|
|
112
|
+
*/
|
|
113
|
+
async function readConfigBody(projectRoot) {
|
|
56
114
|
try {
|
|
57
|
-
|
|
58
|
-
if (config !== null)
|
|
59
|
-
return config;
|
|
60
|
-
return { ...DEFAULT_CONFIG };
|
|
115
|
+
return JSON.parse(await fs.readFile(getFeatureConfigPath(projectRoot), 'utf-8'));
|
|
61
116
|
}
|
|
62
117
|
catch {
|
|
63
|
-
|
|
118
|
+
// ENOENT (absent) or SyntaxError (malformed) — treat as not present
|
|
119
|
+
return undefined;
|
|
64
120
|
}
|
|
65
121
|
}
|
|
66
122
|
/**
|
|
67
|
-
*
|
|
123
|
+
* Serialise a config body to a project's config file.
|
|
68
124
|
* Creates the .devflow/ directory if missing.
|
|
69
125
|
* Uses an atomic temp+rename pattern to prevent partial reads under concurrent writes.
|
|
70
126
|
*/
|
|
71
|
-
|
|
127
|
+
async function writeConfigBody(projectRoot, body) {
|
|
72
128
|
const configPath = getFeatureConfigPath(projectRoot);
|
|
73
129
|
await fs.mkdir(path.join(projectRoot, '.devflow'), { recursive: true });
|
|
74
130
|
const tmpPath = configPath + '.tmp.' + process.pid;
|
|
75
|
-
await fs.writeFile(tmpPath, JSON.stringify(
|
|
131
|
+
await fs.writeFile(tmpPath, JSON.stringify(body, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
76
132
|
await fs.rename(tmpPath, configPath);
|
|
77
133
|
}
|
|
78
134
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
135
|
+
* Merge devflow's managed keys over the config body the file already holds.
|
|
136
|
+
* Pure — returns a new object and never mutates `existing`.
|
|
137
|
+
*
|
|
138
|
+
* D-CONFIG-PRESERVE-UNMANAGED (avoids PF-071): `.devflow/config.json` is a
|
|
139
|
+
* user-editable file that devflow only PARTLY owns. The managed keys come from
|
|
140
|
+
* `managed`; every other key comes from the file, verbatim and by key presence
|
|
141
|
+
* — the hand-written per-repo `tracker` override (whose invalid values must
|
|
142
|
+
* survive so their DEGRADED report can name them) and any key devflow does not
|
|
143
|
+
* know. The alternative, writing the declared shape, is a silent delete of all
|
|
144
|
+
* of them on every `devflow init`. Every writer of the file goes through here
|
|
145
|
+
* (writeManagedConfig, called by init). Only the managed key is copied from
|
|
146
|
+
* `managed`, by name, so a caller holding a whole FeatureConfig still cannot
|
|
147
|
+
* overwrite the file's override with its in-memory copy. The retired keys in
|
|
148
|
+
* RETIRED_CONFIG_KEYS are dropped, not carried. A body that is
|
|
149
|
+
* not a JSON object (absent, malformed, an array) reads as empty, exactly as
|
|
150
|
+
* readConfigIfPresent treats it.
|
|
81
151
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* milliseconds on a local filesystem with no concurrent writers in normal use.
|
|
86
|
-
* If concurrent safety is ever required, replace with an atomic file-swap or
|
|
87
|
-
* a lock file.
|
|
152
|
+
* This holds under `devflow init --reset` too: a factory reset returns
|
|
153
|
+
* devflow's own settings to their defaults through `managed`, and leaves the
|
|
154
|
+
* keys devflow never wrote alone.
|
|
88
155
|
*/
|
|
89
|
-
export
|
|
90
|
-
const
|
|
91
|
-
|
|
156
|
+
export function mergeManagedConfig(existing, managed) {
|
|
157
|
+
const fileKeys = isJsonObject(existing)
|
|
158
|
+
? Object.fromEntries(Object.entries(existing).filter(([key]) => !RETIRED_CONFIG_KEYS.has(key)))
|
|
159
|
+
: {};
|
|
160
|
+
return {
|
|
161
|
+
...fileKeys,
|
|
162
|
+
reviewPublication: managed.reviewPublication,
|
|
163
|
+
};
|
|
92
164
|
}
|
|
93
165
|
/**
|
|
94
|
-
*
|
|
166
|
+
* Write devflow's managed keys to a project's config, keeping every key it
|
|
167
|
+
* does not manage (D-CONFIG-PRESERVE-UNMANAGED).
|
|
168
|
+
*
|
|
169
|
+
* D1: Non-atomic read-modify-write. A concurrent writer could lose the other's
|
|
170
|
+
* change. Acceptable because init is a single-threaded, user-initiated command
|
|
171
|
+
* and the window is milliseconds on a local filesystem; the file swap itself is
|
|
172
|
+
* atomic (temp + rename), so a reader never sees a partial file.
|
|
95
173
|
*/
|
|
96
|
-
export async function
|
|
97
|
-
const
|
|
98
|
-
|
|
174
|
+
export async function writeManagedConfig(projectRoot, managed) {
|
|
175
|
+
const existing = await readConfigBody(projectRoot);
|
|
176
|
+
await writeConfigBody(projectRoot, mergeManagedConfig(existing, managed));
|
|
99
177
|
}
|
|
100
178
|
/**
|
|
101
|
-
* Read the
|
|
179
|
+
* Read the per-repo config for a project root, returning null when the file
|
|
102
180
|
* is absent or malformed.
|
|
103
181
|
*
|
|
104
182
|
* Unlike readConfig (which falls back to DEFAULT_CONFIG on any error),
|
|
105
183
|
* readConfigIfPresent distinguishes "not configured yet" (null) from
|
|
106
|
-
* "configured with specific values" (FeatureConfig).
|
|
107
|
-
*
|
|
108
|
-
* manifest for memory/learning/knowledge even when the manifest is absent.
|
|
109
|
-
*
|
|
110
|
-
* Applies ADR-001: .devflow/config.json is the source of truth; null means
|
|
111
|
-
* the config file does not exist or is unreadable — not that all features
|
|
112
|
-
* are disabled.
|
|
184
|
+
* "configured with specific values" (FeatureConfig). init relies on the
|
|
185
|
+
* distinction to carry a repo's own reviewPublication across a re-init.
|
|
113
186
|
*/
|
|
114
187
|
export async function readConfigIfPresent(projectRoot) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const text = await fs.readFile(configPath, 'utf-8');
|
|
118
|
-
return coerceConfig(JSON.parse(text)); // null when JSON is not a plain object
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
// ENOENT (absent) or SyntaxError (malformed) — treat as not present
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
188
|
+
// null when the file is absent or malformed, or its JSON is not a plain object
|
|
189
|
+
return coerceConfig(await readConfigBody(projectRoot));
|
|
124
190
|
}
|
|
125
191
|
//# sourceMappingURL=feature-config.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { writeFileAtomicExclusive } from './fs-atomic.js';
|
|
4
|
+
function isJsonObject(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The pre-rename key each machine feature was stored under, where one exists:
|
|
9
|
+
* `learning` was `decisions` (ADR-011) and `knowledge` was `kb`. A manifest no
|
|
10
|
+
* command has rewritten since the rename can still hold only the legacy key.
|
|
11
|
+
* `memory` was never renamed.
|
|
12
|
+
*/
|
|
13
|
+
const LEGACY_KEYS = {
|
|
14
|
+
learning: 'decisions',
|
|
15
|
+
knowledge: 'kb',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Whether a RAW parsed manifest leaves `feature` switched on. Pure.
|
|
19
|
+
*
|
|
20
|
+
* Only an explicit boolean `false` switches a feature off. A missing key, a
|
|
21
|
+
* non-boolean value, or anything that is not a manifest-shaped object reads as
|
|
22
|
+
* ON — fail-open (ADR-028), and the exact rule `queue_read_gates` applies in the
|
|
23
|
+
* shell hooks, so the CLI's status and the runtime never disagree about the
|
|
24
|
+
* same file.
|
|
25
|
+
*
|
|
26
|
+
* D-LEARNING-LEGACY-DECISIONS (a sub-decision of D-FEATURES-MACHINE-WIDE):
|
|
27
|
+
* `learning` is read as `features.learning` when that is a boolean, else the
|
|
28
|
+
* legacy `features.decisions` when THAT is a boolean, else ON — readManifest's
|
|
29
|
+
* migration precedence exactly. The legacy key is otherwise honoured only once
|
|
30
|
+
* some command happens to run readManifest and heal it, so a `decisions: false`
|
|
31
|
+
* would keep learning running until then. queue_read_gates applies the same
|
|
32
|
+
* precedence.
|
|
33
|
+
*
|
|
34
|
+
* D-KNOWLEDGE-LEGACY-KB (the same sub-decision for the other renamed key):
|
|
35
|
+
* `knowledge` is read as `features.knowledge` when that is a boolean, else the
|
|
36
|
+
* legacy `features.kb` when THAT is a boolean, else ON — again readManifest's
|
|
37
|
+
* precedence exactly, so `devflow knowledge --status` reports a `kb: false`
|
|
38
|
+
* as disabled. queue_read_gates never reads knowledge, so there is no shell
|
|
39
|
+
* mirror. The knowledge write-back prose gate deliberately does not learn the
|
|
40
|
+
* legacy key (ADR-028: no prompt text for a state only an un-upgraded install
|
|
41
|
+
* can hold); readManifest rewrites `kb` to `knowledge` on the next CLI run
|
|
42
|
+
* that loads the manifest, after which that gate reads the healed key.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately NOT built on readManifest(): it returns null for a manifest
|
|
45
|
+
* missing any of its required fields — reported as "on" here, as the hooks read
|
|
46
|
+
* it — and it writes its heals back to disk, which a read-only status must not.
|
|
47
|
+
*/
|
|
48
|
+
export function isMachineFeatureOn(rawManifest, feature) {
|
|
49
|
+
if (!isJsonObject(rawManifest))
|
|
50
|
+
return true;
|
|
51
|
+
const features = rawManifest.features;
|
|
52
|
+
if (!isJsonObject(features))
|
|
53
|
+
return true;
|
|
54
|
+
const legacyKey = LEGACY_KEYS[feature];
|
|
55
|
+
const value = legacyKey !== undefined && typeof features[feature] !== 'boolean'
|
|
56
|
+
? features[legacyKey]
|
|
57
|
+
: features[feature];
|
|
58
|
+
return value !== false;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Set `feature` in a RAW parsed manifest. Pure — returns a new object and never
|
|
62
|
+
* mutates its input. Null when the value is not a manifest-shaped object (there
|
|
63
|
+
* is no `features` record to write into).
|
|
64
|
+
*
|
|
65
|
+
* Only `features.<feature>` and `updatedAt` change; every other key is carried
|
|
66
|
+
* verbatim. Writing `learning` leaves a legacy `decisions` key in place, and
|
|
67
|
+
* writing `knowledge` a legacy `kb` key, inert: the boolean just written wins
|
|
68
|
+
* over it (D-LEARNING-LEGACY-DECISIONS, D-KNOWLEDGE-LEGACY-KB). Going through
|
|
69
|
+
* readManifest()/writeManifest() instead would refuse a manifest that reader
|
|
70
|
+
* rejects, drop every key ManifestData does not model (one a newer devflow
|
|
71
|
+
* wrote, say), and persist that reader's unrelated heals as a side effect of a
|
|
72
|
+
* one-key toggle.
|
|
73
|
+
*/
|
|
74
|
+
export function setMachineFeature(rawManifest, feature, enabled, now) {
|
|
75
|
+
if (!isJsonObject(rawManifest) || !isJsonObject(rawManifest.features))
|
|
76
|
+
return null;
|
|
77
|
+
return {
|
|
78
|
+
...rawManifest,
|
|
79
|
+
features: { ...rawManifest.features, [feature]: enabled },
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function readRawManifest(devflowDir) {
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(await fs.readFile(path.join(devflowDir, 'manifest.json'), 'utf-8'));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// ENOENT, EACCES or SyntaxError — no usable manifest.
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Read the machine-wide switch from `<devflowDir>/manifest.json`. Read-only (no
|
|
94
|
+
* heal write); an absent, unreadable or malformed manifest reads as ON.
|
|
95
|
+
*/
|
|
96
|
+
export async function readMachineFeature(devflowDir, feature) {
|
|
97
|
+
return isMachineFeatureOn(await readRawManifest(devflowDir), feature);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Write the machine-wide switch to `<devflowDir>/manifest.json` (atomic
|
|
101
|
+
* temp + rename). Refuses with `not-installed` when there is no manifest to
|
|
102
|
+
* write into — a manifest is created by `devflow init`, never by a toggle,
|
|
103
|
+
* because a bare `{features: {...}}` file is not a manifest any reader accepts.
|
|
104
|
+
*/
|
|
105
|
+
export async function writeMachineFeature(devflowDir, feature, enabled) {
|
|
106
|
+
const next = setMachineFeature(await readRawManifest(devflowDir), feature, enabled, new Date().toISOString());
|
|
107
|
+
if (next === null)
|
|
108
|
+
return { ok: false, error: 'not-installed' };
|
|
109
|
+
await writeFileAtomicExclusive(path.join(devflowDir, 'manifest.json'), JSON.stringify(next, null, 2) + '\n');
|
|
110
|
+
return { ok: true, value: undefined };
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=feature-switch.js.map
|
package/dist/core/flags.js
CHANGED
|
@@ -121,8 +121,8 @@ export const FLAG_REGISTRY = [
|
|
|
121
121
|
kind: 'boolean',
|
|
122
122
|
target: { type: 'setting', key: 'disableBundledSkills' },
|
|
123
123
|
onPayload: true,
|
|
124
|
-
recommended:
|
|
125
|
-
defaultValue:
|
|
124
|
+
recommended: false,
|
|
125
|
+
defaultValue: false,
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
128
|
id: 'pin-sonnet-4-6',
|
|
@@ -133,8 +133,8 @@ export const FLAG_REGISTRY = [
|
|
|
133
133
|
kind: 'boolean',
|
|
134
134
|
target: { type: 'env', key: 'ANTHROPIC_DEFAULT_SONNET_MODEL' },
|
|
135
135
|
onPayload: 'claude-sonnet-4-6',
|
|
136
|
-
recommended:
|
|
137
|
-
defaultValue:
|
|
136
|
+
recommended: false,
|
|
137
|
+
defaultValue: false,
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
// Devflow fan-outs routinely exceed the upstream default of 20.
|