devflow-kit 3.3.0 → 3.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 +47 -0
- package/README.md +8 -4
- package/dist/agents/code-edit.md +133 -0
- package/dist/agents/code-fix.md +165 -0
- package/dist/agents/code.md +187 -0
- package/{src/assets → dist}/agents/design.md +1 -2
- package/{src/assets → dist}/agents/diagnose.md +1 -2
- package/dist/agents/git.md +33 -722
- package/{src/assets → dist}/agents/knowledge.md +12 -5
- package/{src/assets → dist}/agents/research.md +8 -2
- package/{src/assets → dist}/agents/review.md +8 -11
- package/{src/assets → dist}/agents/scrutinize.md +4 -1
- package/dist/agents/skim.md +152 -0
- package/{src/assets → dist}/agents/triage.md +9 -2
- package/dist/cli/commands/behavior-prompts.js +77 -0
- package/dist/cli/commands/behavior.js +123 -0
- package/dist/cli/commands/flags.js +8 -2
- package/dist/cli/commands/init-seed.js +7 -3
- package/dist/cli/commands/init.js +134 -5
- package/dist/cli/commands/learning.js +38 -3
- package/dist/cli/commands/rules.js +16 -0
- package/dist/cli/commands/tracker.js +112 -15
- package/dist/cli/commands/uninstall.js +42 -1
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +35 -13
- package/dist/commands/code-review.md +155 -69
- package/dist/commands/debug.md +39 -16
- package/dist/commands/dynamic-build.md +61 -72
- package/dist/commands/dynamic-plan.md +39 -25
- package/dist/commands/dynamic-profile.md +28 -14
- package/dist/commands/dynamic-tickets.md +38 -22
- package/dist/commands/explore.md +18 -16
- package/dist/commands/implement.md +121 -150
- package/dist/commands/plan.md +51 -31
- package/dist/commands/release.md +72 -6
- package/dist/commands/research.md +49 -20
- package/dist/commands/resolve.md +54 -46
- package/dist/commands/self-review.md +38 -25
- package/dist/core/agent-frontmatter.js +101 -0
- package/dist/core/agent-models.js +43 -0
- package/dist/core/assets.js +55 -10
- package/dist/core/claude-md-audit.js +190 -0
- package/dist/core/compliance-compose.js +6 -4
- package/dist/core/feature-switch.js +41 -1
- package/dist/core/flags.js +139 -0
- package/dist/core/fs-atomic.js +8 -3
- package/dist/core/learning-variants.js +213 -0
- package/dist/core/manifest.js +73 -3
- package/dist/core/mds-variants.js +98 -19
- package/dist/core/plugins.js +111 -72
- package/dist/core/project-paths.js +0 -4
- package/dist/core/proxy-log.js +1 -1
- package/dist/core/proxy-state.js +1 -1
- package/dist/core/tracker.js +120 -2
- package/dist/learning-off/agents/code-edit.md +129 -0
- package/dist/learning-off/agents/code-fix.md +161 -0
- package/dist/learning-off/agents/code.md +183 -0
- package/dist/learning-off/agents/design.md +118 -0
- package/dist/learning-off/agents/diagnose.md +210 -0
- package/dist/learning-off/agents/knowledge.md +96 -0
- package/dist/learning-off/agents/research.md +155 -0
- package/dist/learning-off/agents/review.md +224 -0
- package/dist/learning-off/agents/scrutinize.md +120 -0
- package/{src/assets → dist/learning-off}/agents/skim.md +5 -8
- package/dist/learning-off/agents/triage.md +170 -0
- package/dist/learning-off/commands/bug-analysis.md +420 -0
- package/dist/learning-off/commands/code-review.md +530 -0
- package/dist/learning-off/commands/debug.md +314 -0
- package/dist/learning-off/commands/dynamic-build.md +1245 -0
- package/dist/learning-off/commands/dynamic-plan.md +426 -0
- package/dist/learning-off/commands/dynamic-profile.md +214 -0
- package/dist/learning-off/commands/dynamic-tickets.md +634 -0
- package/dist/learning-off/commands/explore.md +210 -0
- package/dist/learning-off/commands/implement.md +774 -0
- package/dist/learning-off/commands/plan.md +671 -0
- package/dist/learning-off/commands/release.md +311 -0
- package/dist/learning-off/commands/research.md +229 -0
- package/dist/learning-off/commands/resolve.md +842 -0
- package/dist/learning-off/commands/self-review.md +266 -0
- package/dist/skills/git/references/learn-conventions.md +27 -3
- package/dist/skills/git/references/pr/check-ci-status.md +19 -0
- package/dist/skills/git/references/pr/check-merge-readiness.md +19 -0
- package/dist/skills/git/references/pr/fetch-review-threads.md +30 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +18 -0
- package/dist/skills/git/references/pr/post-review-summary.md +20 -0
- package/dist/skills/git/references/pr/resolve-review-threads.md +28 -4
- package/dist/skills/git/references/pr/update-pr-evidence.md +13 -0
- package/dist/skills/git/references/pr/validate-branch.md +26 -0
- package/dist/skills/git/references/tracker/_contract.md +33 -0
- package/dist/skills/git/references/tracker/github/associate-release.md +24 -0
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +31 -1
- package/dist/skills/git/references/tracker/github/create-release.md +30 -1
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +89 -2
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +16 -1
- package/dist/skills/git/references/tracker/github/fetch-issue.md +38 -1
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +49 -1
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +29 -0
- package/dist/skills/git/references/tracker/github/manage-debt.md +21 -1
- package/dist/skills/git/references/tracker/github/post-wave-report.md +17 -1
- package/dist/skills/git/references/tracker/github/setup-task.md +53 -1
- package/dist/skills/git/references/tracker/jira/associate-release.md +25 -1
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +31 -1
- package/dist/skills/git/references/tracker/jira/create-release.md +30 -1
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +89 -2
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +16 -1
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +38 -1
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +49 -1
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +29 -0
- package/dist/skills/git/references/tracker/jira/manage-debt.md +21 -1
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +17 -1
- package/dist/skills/git/references/tracker/jira/setup-task.md +55 -3
- package/dist/skills/git/references/tracker/linear/associate-release.md +25 -1
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +31 -1
- package/dist/skills/git/references/tracker/linear/create-release.md +30 -1
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +89 -2
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +16 -1
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +38 -1
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +49 -1
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +29 -0
- package/dist/skills/git/references/tracker/linear/manage-debt.md +21 -1
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +17 -1
- package/dist/skills/git/references/tracker/linear/setup-task.md +55 -3
- package/dist/targets/claude-code/agent-type-owner.js +164 -0
- package/dist/targets/claude-code/behavior-install.js +111 -0
- package/dist/targets/claude-code/installer.js +140 -37
- package/dist/targets/claude-code/language-stamp.js +185 -0
- package/dist/targets/claude-code/learning-install.js +497 -0
- package/dist/targets/claude-code/tracker-install.js +33 -16
- package/dist/targets/claude-code/tracker-servers.js +107 -0
- package/package.json +4 -3
- package/src/assets/agents/_partials/_code.mds +109 -0
- package/src/assets/agents/code-edit.mds +81 -0
- package/src/assets/agents/code-fix.mds +113 -0
- package/src/assets/agents/code.mds +135 -0
- package/src/assets/agents/design.mds +148 -0
- package/src/assets/agents/diagnose.mds +225 -0
- package/src/assets/agents/evaluate.md +8 -4
- package/src/assets/agents/explore.md +58 -0
- package/src/assets/agents/git.mds +33 -722
- package/src/assets/agents/knowledge.mds +131 -0
- package/src/assets/agents/learning.md +19 -69
- package/src/assets/agents/research.mds +182 -0
- package/src/assets/agents/review.mds +282 -0
- package/src/assets/agents/scrutinize.mds +135 -0
- package/src/assets/agents/simplify.md +3 -0
- package/src/assets/agents/skim.mds +165 -0
- package/src/assets/agents/synthesize.md +26 -352
- package/src/assets/agents/test.md +8 -0
- package/src/assets/agents/tracker.md +20 -7
- package/src/assets/agents/triage.mds +201 -0
- package/src/assets/agents/validate.md +13 -6
- package/src/assets/commands/_partials/_compliance.mds +41 -4
- package/src/assets/commands/_partials/_decisions.mds +33 -2
- package/src/assets/commands/_partials/_engine.mds +14 -6
- package/src/assets/commands/_partials/_knowledge.mds +25 -12
- package/src/assets/commands/_partials/_preamble.mds +34 -10
- package/src/assets/commands/_partials/_publication.mds +5 -4
- package/src/assets/commands/_partials/_roster.mds +4 -3
- package/src/assets/commands/_partials/_settings.mds +13 -5
- package/src/assets/commands/_partials/_wave.mds +9 -1
- package/src/assets/commands/bug-analysis.mds +27 -5
- package/src/assets/commands/code-review.mds +158 -52
- package/src/assets/commands/debug.mds +41 -3
- package/src/assets/commands/dynamic-build.mds +47 -27
- package/src/assets/commands/dynamic-plan.mds +40 -9
- package/src/assets/commands/dynamic-profile.mds +9 -1
- package/src/assets/commands/dynamic-tickets.mds +21 -6
- package/src/assets/commands/explore.mds +28 -2
- package/src/assets/commands/implement.mds +126 -129
- package/src/assets/commands/plan.mds +58 -14
- package/src/assets/commands/{release.md → release.mds} +30 -26
- package/src/assets/commands/research.mds +42 -11
- package/src/assets/commands/resolve.mds +59 -15
- package/src/assets/commands/self-review.mds +30 -5
- package/src/assets/mds/.mdsroot +4 -0
- package/src/assets/mds/git/_ops.mds +383 -0
- package/src/assets/mds/git/_ops_pr.mds +201 -0
- package/src/assets/mds/git/_ops_steps.mds +147 -0
- package/src/assets/mds/git/_pr.mds +39 -44
- package/src/assets/mds/git/_references.mds +6 -3
- package/src/assets/mds/tracker/_common.mds +5 -5
- package/src/assets/mds/tracker/_contract.mds +73 -0
- package/src/assets/mds/tracker/_github.mds +73 -16
- package/src/assets/mds/tracker/_jira.mds +77 -19
- package/src/assets/mds/tracker/_linear.mds +77 -19
- package/src/assets/mds/tracker/_mcp.mds +2 -2
- package/src/assets/mds/tracker/_steps.mds +98 -0
- package/src/assets/rules/behavior.md +6 -0
- package/src/assets/rules/context-economy.md +10 -0
- package/src/assets/rules/go.md +1 -0
- package/src/assets/rules/java.md +1 -0
- package/src/assets/rules/python.md +1 -0
- package/src/assets/rules/rust.md +1 -0
- package/src/assets/rules/typescript.md +1 -0
- package/src/assets/scripts/claude-md-audit.cjs +611 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +3 -3
- package/src/assets/scripts/hooks/json-helper.cjs +13 -5
- package/src/assets/scripts/hooks/json-parse +34 -10
- package/src/assets/scripts/hooks/lib/project-paths.cjs +0 -6
- package/src/assets/scripts/hooks/session-start-context +315 -7
- package/src/assets/skills/agent-references/SKILL.md +33 -0
- package/src/assets/skills/agent-references/references/learning/maintain.md +71 -0
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +71 -0
- package/src/assets/skills/agent-references/references/synthesize/design.md +36 -0
- package/src/assets/skills/agent-references/references/synthesize/exploration.md +38 -0
- package/src/assets/skills/agent-references/references/synthesize/planning.md +60 -0
- package/src/assets/skills/agent-references/references/synthesize/research.md +61 -0
- package/src/assets/skills/agent-references/references/synthesize/review.md +76 -0
- package/src/assets/skills/apply-decisions/SKILL.md +1 -1
- package/src/assets/skills/apply-feature-knowledge/SKILL.md +5 -5
- package/src/assets/skills/compliance/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +80 -83
- package/src/assets/skills/docs-framework/references/patterns.md +14 -25
- package/src/assets/skills/docs-framework/references/violations.md +7 -7
- package/src/assets/skills/feature-knowledge/SKILL.md +43 -12
- package/src/assets/skills/gap-analysis/SKILL.md +0 -16
- package/src/assets/skills/gap-analysis/references/compliance.md +19 -0
- package/src/assets/skills/quality-gates/SKILL.md +1 -1
- package/dist/skills/git/references/pr/ensure-pr-ready.md +0 -24
- package/src/assets/agents/code.md +0 -330
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning-variant splitter: one compiled prompt body in, its learning-on and
|
|
3
|
+
* learning-off variants out.
|
|
4
|
+
*
|
|
5
|
+
* Pure module, zero I/O. Every refusal is a Result; the exiting shell is
|
|
6
|
+
* scripts/build-mds.ts, which renders these errors into the build's single exit.
|
|
7
|
+
*
|
|
8
|
+
* D-LEARNING-VARIANTS: when learning is off on a machine, the decisions text is
|
|
9
|
+
* ABSENT from every prompt it would have reached, not merely gated at run time.
|
|
10
|
+
* The build therefore emits two variants of a prompt that carries learning text:
|
|
11
|
+
* the learning-on variant at today's path (dist/commands/x.md, dist/agents/x.md)
|
|
12
|
+
* and the learning-off variant at dist/learning-off/{commands,agents}/x.md, only
|
|
13
|
+
* for a host that has an arm. A prompt with no arm has one variant and no
|
|
14
|
+
* learning-off file.
|
|
15
|
+
*
|
|
16
|
+
* The arms are whole-line markers in the host or in a partial it expands:
|
|
17
|
+
*
|
|
18
|
+
* <!-- learning:on --> opens an arm that only the learning-on variant keeps
|
|
19
|
+
* <!-- learning:off --> opens an arm that only the learning-off variant keeps;
|
|
20
|
+
* directly after an on arm it is that arm's else
|
|
21
|
+
* <!-- learning:end --> closes the arm
|
|
22
|
+
*
|
|
23
|
+
* A marker line matches LEARNING_MARKER_RE. It may be indented and may sit inside
|
|
24
|
+
* a fenced block, a list, a define body or an agent's second frontmatter block,
|
|
25
|
+
* because MDS passes an HTML comment through untouched. The splitter runs on the
|
|
26
|
+
* COMPILED body, after the MDS compile, so it adds no compileFile call, no import
|
|
27
|
+
* into an MDS module and nothing against the per-module compile-time guard. A
|
|
28
|
+
* marker line is removed whole, with its newline. Every other line is kept byte
|
|
29
|
+
* for byte, so a blank line is content: put the blank line a paragraph needs
|
|
30
|
+
* inside the arm that owns the paragraph, and the other variant gets none of it.
|
|
31
|
+
*
|
|
32
|
+
* The markers are INTERIM. MDS 0.4.4 emits an `@if` inside a code fence or on an
|
|
33
|
+
* indented line as literal text (its lexer requires column 0), and the arms have
|
|
34
|
+
* to sit exactly there: inside fenced spawn blocks, scripts, list items and
|
|
35
|
+
* frontmatter. They are replaced by native conditionals once mdscript ships
|
|
36
|
+
* fence- and indent-aware `@if` (dean0x/mdscript#452). That swap, with the
|
|
37
|
+
* @mdscript/mds upgrade it rides on proven byte-identical in dist/ and this module
|
|
38
|
+
* deleted, is dean0x/devflow#439. The mapping is one to one: `on` becomes
|
|
39
|
+
* `@if learning:`, `off` becomes `@else:` and `end` becomes `@end`. A standalone
|
|
40
|
+
* `off` arm, with no on arm before it, becomes an `@if learning:` with an empty
|
|
41
|
+
* body followed by `@else:`.
|
|
42
|
+
*
|
|
43
|
+
* What the splitter refuses, each as a LearningSplitError with the source line:
|
|
44
|
+
* - nesting: an `on` marker while an arm is open, or a second `off` in one arm;
|
|
45
|
+
* - an `end` with no open arm;
|
|
46
|
+
* - an arm still open at the end of the body;
|
|
47
|
+
* - an empty arm (no line with content between its marker and the next);
|
|
48
|
+
* - a stray marker: a line that names `<!-- learning:` but is not a whole-line
|
|
49
|
+
* marker, which would otherwise ship as prompt text.
|
|
50
|
+
*
|
|
51
|
+
* Scope guarantee: this module answers one question (what are the two variants of
|
|
52
|
+
* this body?) and one cheap one for the build to ask of a body that must carry no
|
|
53
|
+
* arms (containsLearningMarker). It decides nothing about WHICH text belongs in an
|
|
54
|
+
* arm: that is authoring, and the guards over the built trees hold it.
|
|
55
|
+
*/
|
|
56
|
+
function Ok(value) {
|
|
57
|
+
return { ok: true, value };
|
|
58
|
+
}
|
|
59
|
+
function Err(error) {
|
|
60
|
+
return { ok: false, error };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Repo-relative root of the learning-off variants.
|
|
64
|
+
*
|
|
65
|
+
* Exported because the build's orphan prune names this directory even when no
|
|
66
|
+
* host carries an arm, which is exactly the case where every file in it is an
|
|
67
|
+
* orphan, so it cannot be derived from the plan.
|
|
68
|
+
*/
|
|
69
|
+
export const LEARNING_OFF_OUTPUT_DIR = 'dist/learning-off';
|
|
70
|
+
/**
|
|
71
|
+
* POSIX repo-relative path of the learning-off variant of an output file.
|
|
72
|
+
*
|
|
73
|
+
* `fileName` is the already-validated basename the learning-on variant is written
|
|
74
|
+
* under (`implement.md`), so the two variants of one host always share a name.
|
|
75
|
+
*/
|
|
76
|
+
export function learningOffRelPath(kind, fileName) {
|
|
77
|
+
return `${LEARNING_OFF_OUTPUT_DIR}/${kind}/${fileName}`;
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Markers
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* A whole-line learning marker. The group captures `on`, `off` or `end`.
|
|
84
|
+
*
|
|
85
|
+
* Anchored at both ends with no nested quantifier, so a match is linear in the
|
|
86
|
+
* line length. Leading and trailing space or tab is allowed; anything else on the
|
|
87
|
+
* line is not a marker.
|
|
88
|
+
*/
|
|
89
|
+
export const LEARNING_MARKER_RE = /^[ \t]*<!-- learning:(on|off|end) -->[ \t]*$/;
|
|
90
|
+
/**
|
|
91
|
+
* The fragment that makes a line a marker ATTEMPT: loose on spacing and case so a
|
|
92
|
+
* malformed marker is caught rather than shipped. Every valid marker contains it.
|
|
93
|
+
*/
|
|
94
|
+
const MARKER_FRAGMENT_RE = /<!--\s*learning\s*:/i;
|
|
95
|
+
/** True when the text names a learning marker anywhere, valid or not. */
|
|
96
|
+
export function containsLearningMarker(text) {
|
|
97
|
+
return MARKER_FRAGMENT_RE.test(text);
|
|
98
|
+
}
|
|
99
|
+
function markerOf(line) {
|
|
100
|
+
// A CRLF body carries the carriage return on the line; it is not part of the marker.
|
|
101
|
+
const bare = line.endsWith('\r') ? line.slice(0, -1) : line;
|
|
102
|
+
const match = LEARNING_MARKER_RE.exec(bare);
|
|
103
|
+
return match === null ? null : match[1];
|
|
104
|
+
}
|
|
105
|
+
/** The longest stretch of a stray line a refusal quotes. */
|
|
106
|
+
const STRAY_QUOTE_MAX = 80;
|
|
107
|
+
/**
|
|
108
|
+
* Split a compiled body into its learning-on and learning-off variants.
|
|
109
|
+
*
|
|
110
|
+
* Total and pure: the same input gives the same output, and a refusal is a
|
|
111
|
+
* Result. The loop is one pass over the body's lines, so its bound is the body's
|
|
112
|
+
* own length.
|
|
113
|
+
*
|
|
114
|
+
* An arm needs a line with content in it. An arm that holds only blank lines is
|
|
115
|
+
* refused as empty, because it would add blank lines to one variant and nothing to
|
|
116
|
+
* the other, which is a mistake rather than a design.
|
|
117
|
+
*/
|
|
118
|
+
export function splitLearningVariants(body) {
|
|
119
|
+
const lines = body.split('\n');
|
|
120
|
+
const on = [];
|
|
121
|
+
const off = [];
|
|
122
|
+
let hasArms = false;
|
|
123
|
+
// The open arm, if any: which one, the line of the marker that opened it, and
|
|
124
|
+
// whether it has held a line with content yet.
|
|
125
|
+
let arm = null;
|
|
126
|
+
let armOpenedAt = 0;
|
|
127
|
+
let armHasContent = false;
|
|
128
|
+
for (let index = 0; index < lines.length; index++) {
|
|
129
|
+
const line = lines[index];
|
|
130
|
+
const lineNo = index + 1;
|
|
131
|
+
const marker = markerOf(line);
|
|
132
|
+
if (marker === null) {
|
|
133
|
+
if (MARKER_FRAGMENT_RE.test(line)) {
|
|
134
|
+
return Err({ kind: 'stray-marker', line: lineNo, text: line.trim().slice(0, STRAY_QUOTE_MAX) });
|
|
135
|
+
}
|
|
136
|
+
if (arm !== 'off')
|
|
137
|
+
on.push(line);
|
|
138
|
+
if (arm !== 'on')
|
|
139
|
+
off.push(line);
|
|
140
|
+
if (arm !== null && line.trim() !== '')
|
|
141
|
+
armHasContent = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
hasArms = true;
|
|
145
|
+
switch (marker) {
|
|
146
|
+
case 'on':
|
|
147
|
+
if (arm !== null)
|
|
148
|
+
return Err({ kind: 'nested-arm', line: lineNo, arm, openedAt: armOpenedAt });
|
|
149
|
+
arm = 'on';
|
|
150
|
+
armOpenedAt = lineNo;
|
|
151
|
+
armHasContent = false;
|
|
152
|
+
break;
|
|
153
|
+
case 'off':
|
|
154
|
+
if (arm === 'off')
|
|
155
|
+
return Err({ kind: 'repeated-off', line: lineNo, openedAt: armOpenedAt });
|
|
156
|
+
// Directly after an on arm this is its else: the on arm ends here.
|
|
157
|
+
if (arm === 'on' && !armHasContent) {
|
|
158
|
+
return Err({ kind: 'empty-arm', arm, openedAt: armOpenedAt, closedAt: lineNo });
|
|
159
|
+
}
|
|
160
|
+
arm = 'off';
|
|
161
|
+
armOpenedAt = lineNo;
|
|
162
|
+
armHasContent = false;
|
|
163
|
+
break;
|
|
164
|
+
case 'end':
|
|
165
|
+
if (arm === null)
|
|
166
|
+
return Err({ kind: 'unopened-end', line: lineNo });
|
|
167
|
+
if (!armHasContent) {
|
|
168
|
+
return Err({ kind: 'empty-arm', arm, openedAt: armOpenedAt, closedAt: lineNo });
|
|
169
|
+
}
|
|
170
|
+
arm = null;
|
|
171
|
+
break;
|
|
172
|
+
default: {
|
|
173
|
+
const unhandled = marker;
|
|
174
|
+
return Err({ kind: 'stray-marker', line: lineNo, text: String(unhandled) });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (arm !== null)
|
|
179
|
+
return Err({ kind: 'unclosed-arm', arm, openedAt: armOpenedAt });
|
|
180
|
+
return Ok({ on: on.join('\n'), off: off.join('\n'), hasArms });
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Render a refusal as one line a person can act on.
|
|
184
|
+
*
|
|
185
|
+
* One arm per kind with a `never` default, so a kind added to the union cannot
|
|
186
|
+
* fall through into a message that does not describe it. Shared by the build and
|
|
187
|
+
* the tests, so the text asserted is the text shipped.
|
|
188
|
+
*/
|
|
189
|
+
export function describeLearningSplitError(error) {
|
|
190
|
+
switch (error.kind) {
|
|
191
|
+
case 'nested-arm':
|
|
192
|
+
return (`line ${error.line}: a learning:on marker inside the ${error.arm} arm opened at line ` +
|
|
193
|
+
`${error.openedAt} — arms do not nest; close it with learning:end first`);
|
|
194
|
+
case 'repeated-off':
|
|
195
|
+
return (`line ${error.line}: a second learning:off marker inside the off arm opened at line ` +
|
|
196
|
+
`${error.openedAt} — an arm has at most one else`);
|
|
197
|
+
case 'unopened-end':
|
|
198
|
+
return `line ${error.line}: a learning:end marker with no open arm`;
|
|
199
|
+
case 'unclosed-arm':
|
|
200
|
+
return `the ${error.arm} arm opened at line ${error.openedAt} is never closed with learning:end`;
|
|
201
|
+
case 'empty-arm':
|
|
202
|
+
return (`lines ${error.openedAt}-${error.closedAt}: the ${error.arm} arm holds no line with content — ` +
|
|
203
|
+
`delete the arm or put its text inside it`);
|
|
204
|
+
case 'stray-marker':
|
|
205
|
+
return (`line ${error.line}: "${error.text}" names a learning marker but is not one — a marker ` +
|
|
206
|
+
`is a whole line, exactly <!-- learning:on -->, <!-- learning:off --> or <!-- learning:end -->`);
|
|
207
|
+
default: {
|
|
208
|
+
const unhandled = error;
|
|
209
|
+
return `unhandled learning split error ${JSON.stringify(unhandled)}`;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=learning-variants.js.map
|
package/dist/core/manifest.js
CHANGED
|
@@ -62,12 +62,15 @@ function parseManifestFlags(features, knownFlags) {
|
|
|
62
62
|
* - features.knownFlags stripped from result (folded into FlagsRecord key-presence)
|
|
63
63
|
* - features.proxy absent → false
|
|
64
64
|
* - features.compliance absent/malformed → {enabled:false, frameworks:[]}
|
|
65
|
+
* - features.behavior absent → false; malformed → false, healed on disk (D-BEHAVIOR-OPT-IN)
|
|
65
66
|
* - features.tracker absent/malformed → {provider:'github'}
|
|
67
|
+
* - features.tracker.servers absent → the default list; invalid → the default list, reported
|
|
68
|
+
* through `opts.onWarning` when the caller supplies one (this reader has no output channel)
|
|
66
69
|
*
|
|
67
70
|
* D39: heal-write failure returns the migrated in-memory manifest (not null).
|
|
68
71
|
* The on-disk format remains unhealed; next read triggers another attempt.
|
|
69
72
|
*/
|
|
70
|
-
export async function readManifest(devflowDir) {
|
|
73
|
+
export async function readManifest(devflowDir, opts) {
|
|
71
74
|
const manifestPath = path.join(devflowDir, 'manifest.json');
|
|
72
75
|
try {
|
|
73
76
|
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
@@ -129,7 +132,10 @@ export async function readManifest(devflowDir) {
|
|
|
129
132
|
features.decisions !== undefined ||
|
|
130
133
|
flagsWereLegacy ||
|
|
131
134
|
features.knownFlags !== undefined ||
|
|
132
|
-
features.viewMode !== undefined
|
|
135
|
+
features.viewMode !== undefined ||
|
|
136
|
+
// D-BEHAVIOR-OPT-IN: a malformed switch (present, not a boolean) is rewritten as
|
|
137
|
+
// the false it reads as; an absent one is not a defect and owes no write.
|
|
138
|
+
(features.behavior !== undefined && typeof features.behavior !== 'boolean');
|
|
133
139
|
const SECURITY_MODES = ['none', 'user', 'managed'];
|
|
134
140
|
const manifest = {
|
|
135
141
|
version: data.version,
|
|
@@ -158,12 +164,14 @@ export async function readManifest(devflowDir) {
|
|
|
158
164
|
proxy: typeof features.proxy === 'boolean' ? features.proxy : false,
|
|
159
165
|
// Self-heal: absent/malformed compliance → {enabled:false, frameworks:[]}
|
|
160
166
|
compliance: normalizeComplianceFeature(features.compliance),
|
|
167
|
+
// D-BEHAVIOR-OPT-IN: absent/malformed → false (opt-in; the inverse of knowledge/learning).
|
|
168
|
+
behavior: features.behavior === true,
|
|
161
169
|
// Self-heal: absent/malformed/unknown tracker → {provider:'github'} (AC-3.21).
|
|
162
170
|
// Deliberately NOT in the hard-null set above — see the field's doc comment.
|
|
163
171
|
// Healing here is silent and emits no DEGRADED: that is the correct
|
|
164
172
|
// self-heal behaviour, and a different condition from a per-repo config
|
|
165
173
|
// value outside the domain (which does emit `unknown tracker provider`).
|
|
166
|
-
tracker: normalizeTrackerFeature(features.tracker),
|
|
174
|
+
tracker: normalizeTrackerFeature(features.tracker, opts?.onWarning),
|
|
167
175
|
},
|
|
168
176
|
installedAt: data.installedAt,
|
|
169
177
|
updatedAt: data.updatedAt,
|
|
@@ -213,6 +221,68 @@ export async function syncManifestFeature(devflowDir, key, value) {
|
|
|
213
221
|
manifest.updatedAt = new Date().toISOString();
|
|
214
222
|
await writeManifest(devflowDir, manifest);
|
|
215
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Drop `names` from a RAW parsed manifest's `plugins` list. Pure — returns a new
|
|
226
|
+
* object and never mutates its input. Null when there is nothing to write: the
|
|
227
|
+
* value is not a manifest-shaped object, or none of `names` is listed.
|
|
228
|
+
*
|
|
229
|
+
* Only `plugins` and `updatedAt` change; every other key is carried verbatim,
|
|
230
|
+
* `knownPlugins` included. That snapshot records which registry plugins the last
|
|
231
|
+
* install knew, and init adopts a registry plugin that is in neither list as new
|
|
232
|
+
* (`resolveSeedPlugins`), so dropping a removed plugin from it would make the next
|
|
233
|
+
* re-init install it again. Going through readManifest()/writeManifest() instead
|
|
234
|
+
* would drop every key ManifestData does not model (one a newer devflow wrote,
|
|
235
|
+
* say) and persist that reader's unrelated heals as a side effect of a removal.
|
|
236
|
+
* Entries that are not strings are neither matched nor dropped.
|
|
237
|
+
*/
|
|
238
|
+
export function withoutManifestPlugins(rawManifest, names, now) {
|
|
239
|
+
if (typeof rawManifest !== 'object' || rawManifest === null || Array.isArray(rawManifest))
|
|
240
|
+
return null;
|
|
241
|
+
const record = rawManifest;
|
|
242
|
+
if (!Array.isArray(record.plugins))
|
|
243
|
+
return null;
|
|
244
|
+
const doomed = new Set(names);
|
|
245
|
+
const kept = record.plugins.filter(entry => !(typeof entry === 'string' && doomed.has(entry)));
|
|
246
|
+
if (kept.length === record.plugins.length)
|
|
247
|
+
return null;
|
|
248
|
+
return { ...record, plugins: kept, updatedAt: now };
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* D-UNINSTALL-DROPS-PLUGIN: record a selective uninstall in `<devflowDir>/manifest.json`.
|
|
252
|
+
*
|
|
253
|
+
* The manifest is the install record init seeds from (a re-init keeps the prior
|
|
254
|
+
* selection), so a plugin `devflow uninstall --plugin` removed but the manifest
|
|
255
|
+
* still lists would come back on the next plain `devflow init`. This is the one
|
|
256
|
+
* writer that takes a name off that list (atomic temp + rename).
|
|
257
|
+
*
|
|
258
|
+
* A quiet no-op, not an error, when there is nothing to record: no manifest (a
|
|
259
|
+
* pre-manifest install), one that is not valid JSON or not manifest-shaped, or
|
|
260
|
+
* none of `names` listed — the file is left byte-identical. Never throws: a failed
|
|
261
|
+
* write comes back as `{ ok: false }` for the caller to report.
|
|
262
|
+
*/
|
|
263
|
+
export async function removeManifestPlugins(devflowDir, names) {
|
|
264
|
+
const manifestPath = path.join(devflowDir, 'manifest.json');
|
|
265
|
+
let raw;
|
|
266
|
+
try {
|
|
267
|
+
raw = JSON.parse(await fs.readFile(manifestPath, 'utf-8'));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// ENOENT, EACCES or SyntaxError — no usable manifest, so nothing to record.
|
|
271
|
+
return { ok: true, removed: [] };
|
|
272
|
+
}
|
|
273
|
+
const next = withoutManifestPlugins(raw, names, new Date().toISOString());
|
|
274
|
+
if (next === null)
|
|
275
|
+
return { ok: true, removed: [] };
|
|
276
|
+
try {
|
|
277
|
+
await writeFileAtomicExclusive(manifestPath, JSON.stringify(next, null, 2) + '\n');
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
281
|
+
}
|
|
282
|
+
const doomed = new Set(names);
|
|
283
|
+
const before = raw.plugins;
|
|
284
|
+
return { ok: true, removed: before.filter((entry) => typeof entry === 'string' && doomed.has(entry)) };
|
|
285
|
+
}
|
|
216
286
|
/**
|
|
217
287
|
* Merge new plugins into existing plugin list (union, no duplicates).
|
|
218
288
|
* Preserves order: existing plugins first, then new ones appended.
|
|
@@ -120,6 +120,13 @@ export function validateContractOutputName(name) {
|
|
|
120
120
|
* from here keeps the table below the only place a destination is spelled.
|
|
121
121
|
*/
|
|
122
122
|
export const AGENTS_OUTPUT_DIR = 'dist/agents';
|
|
123
|
+
/**
|
|
124
|
+
* Repo-relative destination for `commands` hosts.
|
|
125
|
+
*
|
|
126
|
+
* Exported for the same reason as AGENTS_OUTPUT_DIR: the orphan prune names the
|
|
127
|
+
* directory even when no command host is planned.
|
|
128
|
+
*/
|
|
129
|
+
export const COMMANDS_OUTPUT_DIR = 'dist/commands';
|
|
123
130
|
/**
|
|
124
131
|
* The bare (unprefixed) skill that OWNS the generated references.
|
|
125
132
|
*
|
|
@@ -153,7 +160,7 @@ export const SKILL_REFS_SKILL_NAME = 'git';
|
|
|
153
160
|
*/
|
|
154
161
|
export const SKILL_REFS_OUTPUT_DIR = `dist/skills/${SKILL_REFS_SKILL_NAME}/references`;
|
|
155
162
|
const ALLOWED_OUTPUT_DIRS = [
|
|
156
|
-
{ dir:
|
|
163
|
+
{ dir: COMMANDS_OUTPUT_DIR, variant: 'commands' },
|
|
157
164
|
{ dir: AGENTS_OUTPUT_DIR, variant: 'agents' },
|
|
158
165
|
{ dir: SKILL_REFS_OUTPUT_DIR, variant: 'skill-refs' },
|
|
159
166
|
];
|
|
@@ -264,32 +271,38 @@ export const TRACKER_OPS = [
|
|
|
264
271
|
*/
|
|
265
272
|
export const TRACKER_GITHUB_OPS = TRACKER_OPS;
|
|
266
273
|
/**
|
|
267
|
-
* The
|
|
274
|
+
* The 8 PR/review operations whose references are generated once, for every
|
|
268
275
|
* provider, under `pr/`.
|
|
269
276
|
*
|
|
270
277
|
* A PR-HOST roster, not a tracker roster, and the distinction is the whole
|
|
271
278
|
* reason this list exists separately from {@link TRACKER_OPS}: pull requests, PR
|
|
272
279
|
* reviews and PR checks stay on GitHub under every issue-tracker provider, so
|
|
273
|
-
* these
|
|
274
|
-
* them under `tracker/github/` would make a jira user's PR mechanics read
|
|
275
|
-
* their tracker's, and fanning them across the three provider directories
|
|
276
|
-
* ship three identical trees.
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
280
|
+
* these operations are the SAME file whatever `TRACKER_PROVIDER` resolves to.
|
|
281
|
+
* Filing them under `tracker/github/` would make a jira user's PR mechanics read
|
|
282
|
+
* as their tracker's, and fanning them across the three provider directories
|
|
283
|
+
* would ship three identical trees.
|
|
284
|
+
*
|
|
285
|
+
* D-OP-ONE-REF. Each file is the WHOLE operation: its contract (description,
|
|
286
|
+
* `**Input:**`, degradation notes, `**Output:**` template, written once in
|
|
287
|
+
* `src/assets/mds/git/_ops.mds`) and then its steps. The operation loads this one
|
|
288
|
+
* file and no other operation reference.
|
|
289
|
+
*
|
|
290
|
+
* `ensure-pr-ready` is deliberately NOT a member. It is a tracker operation: step
|
|
291
|
+
* 4b, the issue-number lookup and the link line it renders, is a tracker fact and
|
|
292
|
+
* differs per provider, so the operation is one file per provider under
|
|
293
|
+
* `tracker/{provider}/ensure-pr-ready.md`. That file carries the PR skeleton
|
|
294
|
+
* (branch/commit/push, create, retitle, and step 4b's open-PR lookup and body edit)
|
|
295
|
+
* as well, written once in `_ops.mds` and expanded into all three, so the PR-host
|
|
296
|
+
* half is the same bytes under every provider and the operation still loads one
|
|
297
|
+
* file.
|
|
298
|
+
*
|
|
299
|
+
* 8 entries, exactly {@link MIN_VARIANT_PAIRS}, which is a floor and not a
|
|
286
300
|
* target: a shorter roster makes every parity assertion over it vacuous (GAP-42)
|
|
287
|
-
* and `expandVariants` refuses the build, so the roster can grow
|
|
288
|
-
*
|
|
289
|
-
*
|
|
301
|
+
* and `expandVariants` refuses the build, so the roster can grow but never drop
|
|
302
|
+
* below 8. `update-pr-evidence` (#363) is the newest member: it edits the PR body
|
|
303
|
+
* and comments on the PR, both GitHub whatever the tracker is.
|
|
290
304
|
*/
|
|
291
305
|
export const PR_HOST_OPS = [
|
|
292
|
-
'ensure-pr-ready',
|
|
293
306
|
'validate-branch',
|
|
294
307
|
'post-review-summary',
|
|
295
308
|
'check-ci-status',
|
|
@@ -347,6 +360,44 @@ export const GIT_CROSS_CUTTING_DOCS = [
|
|
|
347
360
|
'publication-gate',
|
|
348
361
|
'trust-rule',
|
|
349
362
|
];
|
|
363
|
+
/**
|
|
364
|
+
* The Git operations whose one operation reference is a cross-cutting document
|
|
365
|
+
* rather than a per-op file in `pr/` or `tracker/{provider}/` (D-OP-ONE-REF).
|
|
366
|
+
*
|
|
367
|
+
* `learn-conventions` is the only one: it has no PR-host half and no provider half,
|
|
368
|
+
* so its reference is the named document {@link GIT_CROSS_CUTTING_DOCS} already emits
|
|
369
|
+
* at the references root. Its contract is compiled into that document like every
|
|
370
|
+
* other operation's (D-OP-CONTRACT-ONCE).
|
|
371
|
+
*/
|
|
372
|
+
export const DOC_BACKED_OPS = ['learn-conventions'];
|
|
373
|
+
/**
|
|
374
|
+
* Every operation the Git agent routes, derived from the three rosters that emit its
|
|
375
|
+
* reference — never retyped. Each one loads exactly one operation reference
|
|
376
|
+
* ({@link operationReferenceRel}).
|
|
377
|
+
*/
|
|
378
|
+
export const GIT_OPERATIONS = [...PR_HOST_OPS, ...TRACKER_OPS, ...DOC_BACKED_OPS];
|
|
379
|
+
/**
|
|
380
|
+
* The skill-relative path (under `references/`) of the one reference an operation
|
|
381
|
+
* loads, or `null` for a name that is not a Git operation.
|
|
382
|
+
*
|
|
383
|
+
* D-OP-ONE-REF. A PR-host operation is `pr/{op}.md`, the same file under every
|
|
384
|
+
* provider; a tracker operation is `tracker/{provider}/{op}.md` for the resolved
|
|
385
|
+
* provider; a doc-backed operation is its named document. The Git agent's
|
|
386
|
+
* `## Loading the mechanics` table states these paths in prose, and
|
|
387
|
+
* `tests/tracker/reference-reachability.test.ts` holds the table to this function.
|
|
388
|
+
*
|
|
389
|
+
* Total and pure: an unknown operation is `null`, never a throw. `ensure-pr-ready` is
|
|
390
|
+
* a tracker operation, so it resolves per provider even though its PR half is shared.
|
|
391
|
+
*/
|
|
392
|
+
export function operationReferenceRel(op, provider) {
|
|
393
|
+
if (TRACKER_OPS.includes(op))
|
|
394
|
+
return `${TRACKER_DESTINATION_ROOT}/${provider}/${op}.md`;
|
|
395
|
+
if (PR_HOST_OPS.includes(op))
|
|
396
|
+
return `${PR_HOST_DESTINATION_ROOT}/${op}.md`;
|
|
397
|
+
if (DOC_BACKED_OPS.includes(op))
|
|
398
|
+
return `${op}.md`;
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
350
401
|
/**
|
|
351
402
|
* Every reference module the build knows about — a closed registry, read the
|
|
352
403
|
* same way ALLOWED_OUTPUT_DIRS is read.
|
|
@@ -363,6 +414,28 @@ export const GIT_CROSS_CUTTING_DOCS = [
|
|
|
363
414
|
* Registering a provider whose `subdir` is one of MCP_BACKED_PROVIDER_SUBDIRS is
|
|
364
415
|
* also what opens the generation gate on the tool-call contract; see
|
|
365
416
|
* {@link mcpContractIsGenerated}. There is no second edit and no flag.
|
|
417
|
+
*
|
|
418
|
+
* D-TRACKER-CONTRACT-ON-DEMAND. The last entry, `_contract.mds`, is the first
|
|
419
|
+
* UNGATED `kind: 'contract'` module (the tool-call contract of
|
|
420
|
+
* {@link MCP_CONTRACT_MODULE} is gated, so it is not in this array). It emits
|
|
421
|
+
* `tracker/_contract.md`: the provider resolution and the tracker input contract
|
|
422
|
+
* that used to sit in the always-loaded Git agent, where every spawn paid for
|
|
423
|
+
* them although most spawns run a PR-host operation that never touches the
|
|
424
|
+
* issue tracker. A spawn now reads the file once, only when it runs a tracker
|
|
425
|
+
* operation, before its first tracker step. The agent's `## Loading the mechanics`
|
|
426
|
+
* section is the one line that names it; it keeps the one-reference rule and the
|
|
427
|
+
* table naming every non-tracker operation's reference, which a PR-only spawn
|
|
428
|
+
* needs and which this file cannot instruct a spawn to load. It is ungated for
|
|
429
|
+
* the reason the registry's other provider-independent files are: every install
|
|
430
|
+
* carries every provider, GitHub included, so no registered provider is the
|
|
431
|
+
* condition it depends on.
|
|
432
|
+
*
|
|
433
|
+
* The budget clause: the file is a per-SPAWN term of each tracker row (GitHub,
|
|
434
|
+
* Jira, Linear), charged the way the tool-call contract is (`contractTerm` in
|
|
435
|
+
* tests/tracker/budget-model.ts) and added in `providerLoadedSet`. It is not part
|
|
436
|
+
* of `ownLoadForProvider`, of the PR-host row or of the per-operation load cap
|
|
437
|
+
* (D-OP-LOAD-CAP): the cap prices one operation's own reference, which is the same
|
|
438
|
+
* whichever tracker row it is read through.
|
|
366
439
|
*/
|
|
367
440
|
export const VARIANT_MODULES = [
|
|
368
441
|
{
|
|
@@ -395,6 +468,12 @@ export const VARIANT_MODULES = [
|
|
|
395
468
|
kind: 'named',
|
|
396
469
|
ops: GIT_CROSS_CUTTING_DOCS,
|
|
397
470
|
},
|
|
471
|
+
{
|
|
472
|
+
source: 'src/assets/mds/tracker/_contract.mds',
|
|
473
|
+
subdir: 'tracker',
|
|
474
|
+
kind: 'contract',
|
|
475
|
+
ops: ['_contract'],
|
|
476
|
+
},
|
|
398
477
|
];
|
|
399
478
|
// ---------------------------------------------------------------------------
|
|
400
479
|
// The tool-call contract module, and the gate on its generation
|