devflow-kit 3.9.0 → 3.11.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 +142 -0
- package/README.md +1 -1
- package/dist/agents/code.md +6 -6
- package/dist/agents/fix.md +6 -6
- package/dist/agents/harden.md +7 -6
- package/dist/agents/refactor.md +6 -6
- package/dist/cli/commands/ambient.js +10 -2
- package/dist/cli/commands/backup-session.js +131 -0
- package/dist/cli/commands/compliance-prompts.js +9 -4
- package/dist/cli/commands/compliance.js +184 -24
- package/dist/cli/commands/curation.js +23 -10
- package/dist/cli/commands/gate-level-prompts.js +39 -25
- package/dist/cli/commands/gates.js +5 -7
- package/dist/cli/commands/init.js +169 -184
- package/dist/cli/commands/install-report.js +129 -0
- package/dist/cli/commands/legacy-sweep.js +159 -0
- package/dist/cli/commands/proxy.js +6 -1
- package/dist/cli/commands/rules.js +75 -21
- package/dist/cli/commands/skills.js +42 -9
- package/dist/cli/commands/tracker.js +9 -3
- package/dist/cli/commands/uninstall.js +339 -75
- package/dist/commands/bug-analysis.md +69 -120
- package/dist/commands/dynamic-build.md +23 -23
- package/dist/commands/implement.md +46 -32
- package/dist/commands/release.md +1 -1
- package/dist/commands/resolve.md +35 -31
- package/dist/commands/self-review.md +8 -7
- 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/assets.js +126 -17
- package/dist/core/compliance-off-files.js +27 -0
- package/dist/core/compliance.js +14 -0
- package/dist/core/curation-variants.js +460 -112
- package/dist/core/flags.js +2 -2
- package/dist/core/fs-atomic.js +5 -1
- 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 +29 -2
- package/dist/core/path-set.js +15 -0
- package/dist/core/plugins.js +68 -10
- package/dist/core/reference-sweep.js +42 -25
- package/dist/core/retired-shipped-names.js +278 -0
- 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 +6 -6
- package/dist/curation-off/agents/fix.md +6 -6
- package/dist/curation-off/agents/harden.md +7 -6
- package/dist/curation-off/agents/refactor.md +6 -6
- package/dist/curation-off/commands/bug-analysis.md +69 -119
- package/dist/curation-off/commands/dynamic-build.md +23 -23
- package/dist/curation-off/commands/implement.md +46 -32
- package/dist/curation-off/commands/release.md +1 -1
- package/dist/curation-off/commands/resolve.md +35 -31
- package/dist/curation-off/commands/self-review.md +8 -7
- package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
- 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 +62 -17
- package/dist/targets/claude-code/compliance-off-files.js +249 -0
- package/dist/targets/claude-code/curation-install.js +632 -117
- package/dist/targets/claude-code/folder-swap.js +142 -0
- package/dist/targets/claude-code/install-backup.js +780 -0
- package/dist/targets/claude-code/installer.js +795 -199
- package/dist/targets/claude-code/language-stamp.js +6 -0
- package/dist/targets/claude-code/legacy-skill-hashes.js +170 -0
- package/dist/targets/claude-code/linked-folder.js +176 -0
- package/dist/targets/claude-code/reference-converge.js +131 -0
- package/dist/targets/claude-code/rules-folder.js +34 -0
- package/dist/targets/claude-code/shipped-set.js +160 -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 +251 -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/evaluate.md +1 -1
- package/src/assets/agents/git.mds +32 -0
- package/src/assets/agents/harden.mds +7 -6
- package/src/assets/agents/test.md +21 -12
- package/src/assets/agents/triage.mds +2 -0
- package/src/assets/agents/validate.md +10 -10
- package/src/assets/commands/_partials/_compliance.mds +5 -3
- package/src/assets/commands/_partials/_engine.mds +9 -8
- package/src/assets/commands/_partials/_publication.mds +10 -1
- package/src/assets/commands/bug-analysis.mds +80 -126
- package/src/assets/commands/code-review.mds +43 -0
- 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 +217 -32
- package/src/assets/commands/self-review.mds +8 -7
- package/src/assets/mds/git/_ops.mds +23 -0
- package/src/assets/mds/git/_ops_steps.mds +17 -0
- package/src/assets/mds/git/_pr.mds +1 -1
- package/src/assets/mds/git/_references.mds +6 -0
- package/src/assets/mds/tracker/_common.mds +4 -0
- package/src/assets/mds/tracker/_github.mds +42 -0
- package/src/assets/mds/tracker/_jira.mds +43 -0
- package/src/assets/mds/tracker/_linear.mds +43 -0
- package/src/assets/rules/quality.md +2 -0
- package/src/assets/scripts/ci-wait.cjs +204 -126
- 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/preamble +18 -8
- package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
- package/src/assets/scripts/pr-evidence.cjs +41 -2
- package/src/assets/scripts/verify-evidence.cjs +408 -56
- 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/synthesize/bug-analysis.md +6 -2
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +7 -7
- 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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shipped set: every file path, relative to the Claude dir, that some install of this build
|
|
3
|
+
* can write into `commands/devflow`, `agents/devflow`, `rules/devflow` and `skills/devflow:*`.
|
|
4
|
+
*
|
|
5
|
+
* The backup session moves a file out of those folders unless the set names it, so the set errs
|
|
6
|
+
* toward too large. It comes from the full registries and all four variants (compliance on and
|
|
7
|
+
* off, curation on and off), never from the selection of one run and never from what an earlier
|
|
8
|
+
* install left on disk:
|
|
9
|
+
*
|
|
10
|
+
* - commands: every plugin command, also a workflow that ships as a skill folder, since the
|
|
11
|
+
* old command file of such a workflow is removed as a shipped file and never moved;
|
|
12
|
+
* - agents and rules: every plugin entry, the feature-owned rules and the tracker agent;
|
|
13
|
+
* - skills: the files of each registry skill in the package source, the generated references
|
|
14
|
+
* of the compliance-on manifest, the compliance skill the converge writes, and the files only
|
|
15
|
+
* a compliance-on install writes;
|
|
16
|
+
* - workflow folders: the files of each registered folder under all four variant keys;
|
|
17
|
+
* - shadows: the listing of a valid skill shadow counts beside the listing it replaces.
|
|
18
|
+
*
|
|
19
|
+
* {@link buildShippedSet} is pure. {@link loadShippedSet} reads the package and the shadows from
|
|
20
|
+
* disk, within fixed bounds, and answers with a reason instead of a smaller set when it cannot.
|
|
21
|
+
*/
|
|
22
|
+
import { promises as fs } from 'fs';
|
|
23
|
+
import * as path from 'path';
|
|
24
|
+
import { skillsDir } from '../../core/assets.js';
|
|
25
|
+
import { complianceSkillFiles } from '../../core/compliance.js';
|
|
26
|
+
import { COMPLIANCE_OFF_REMOVED_SKILL_FILES } from '../../core/compliance-off-files.js';
|
|
27
|
+
import { VARIANT_KEYS } from '../../core/curation-variants.js';
|
|
28
|
+
import { SKILL_REFS_SKILL_NAME, installedReferenceManifest, workflowFolderFiles, } from '../../core/mds-variants.js';
|
|
29
|
+
import { createPathSet } from '../../core/path-set.js';
|
|
30
|
+
import { FEATURE_OWNED_RULES, getAllAgentNames, getAllCommandNames, getAllRuleNames, getAllSkillNames, prefixSkillName, } from '../../core/plugins.js';
|
|
31
|
+
import { validateSkillShadow } from './installer.js';
|
|
32
|
+
import { TRACKER_AGENT_NAME } from './tracker-install.js';
|
|
33
|
+
/** The most files one listed skill folder may hold. A folder past it is a failed listing. */
|
|
34
|
+
export const MAX_LISTED_FILES = 2_000;
|
|
35
|
+
/** The deepest level a listed folder may reach, counting the listed root as 0. */
|
|
36
|
+
export const MAX_LISTED_DEPTH = 6;
|
|
37
|
+
/** The most folders one listing may visit. */
|
|
38
|
+
export const MAX_LISTED_FOLDERS = 500;
|
|
39
|
+
const IGNORED_FILE_NAME = '.DS_Store';
|
|
40
|
+
const commandPath = (name) => `commands/devflow/${name}.md`;
|
|
41
|
+
const agentPath = (name) => `agents/devflow/${name}.md`;
|
|
42
|
+
const rulePath = (name) => `rules/devflow/${name}.md`;
|
|
43
|
+
const skillPath = (skill, file) => `skills/${prefixSkillName(skill)}/${file}`;
|
|
44
|
+
/**
|
|
45
|
+
* The file paths of the shipped set. Pure: the registries are constants, and the package and
|
|
46
|
+
* shadow listings come in as inputs.
|
|
47
|
+
*/
|
|
48
|
+
export function shippedPaths(inputs) {
|
|
49
|
+
const paths = new Set();
|
|
50
|
+
for (const name of getAllCommandNames(new Set()))
|
|
51
|
+
paths.add(commandPath(name));
|
|
52
|
+
for (const name of [...getAllAgentNames(), TRACKER_AGENT_NAME])
|
|
53
|
+
paths.add(agentPath(name));
|
|
54
|
+
for (const name of [...getAllRuleNames(), ...FEATURE_OWNED_RULES])
|
|
55
|
+
paths.add(rulePath(name));
|
|
56
|
+
for (const [skill, files] of inputs.packageSkillFiles) {
|
|
57
|
+
for (const file of files)
|
|
58
|
+
paths.add(skillPath(skill, file));
|
|
59
|
+
}
|
|
60
|
+
for (const [skill, files] of inputs.shadowListings ?? []) {
|
|
61
|
+
for (const file of files)
|
|
62
|
+
paths.add(skillPath(skill, file));
|
|
63
|
+
}
|
|
64
|
+
for (const rel of installedReferenceManifest({ compliance: true })) {
|
|
65
|
+
paths.add(skillPath(SKILL_REFS_SKILL_NAME, `references/${rel}`));
|
|
66
|
+
}
|
|
67
|
+
for (const entry of COMPLIANCE_OFF_REMOVED_SKILL_FILES)
|
|
68
|
+
paths.add(`skills/${entry}`);
|
|
69
|
+
for (const file of complianceSkillFiles())
|
|
70
|
+
paths.add(skillPath('compliance', file));
|
|
71
|
+
for (const folder of inputs.workflowFolders) {
|
|
72
|
+
for (const key of VARIANT_KEYS) {
|
|
73
|
+
for (const file of workflowFolderFiles(folder, key))
|
|
74
|
+
paths.add(skillPath(folder.name, file));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return paths;
|
|
78
|
+
}
|
|
79
|
+
/** The shipped set of {@link shippedPaths}, ready for the backup session. */
|
|
80
|
+
export function buildShippedSet(inputs) {
|
|
81
|
+
return createPathSet(shippedPaths(inputs));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The files below `root` as POSIX paths, sorted per folder. Folders are entered only as real
|
|
85
|
+
* folders (a link is listed as a file and never entered). The listing is bounded by
|
|
86
|
+
* {@link MAX_LISTED_FILES}, {@link MAX_LISTED_DEPTH} and {@link MAX_LISTED_FOLDERS}; a breach or a
|
|
87
|
+
* read error is a failed listing, since a smaller listing would be a smaller set.
|
|
88
|
+
*/
|
|
89
|
+
async function listFiles(root) {
|
|
90
|
+
const files = [];
|
|
91
|
+
const pending = [
|
|
92
|
+
{ dir: root, rel: '', depth: 0 },
|
|
93
|
+
];
|
|
94
|
+
for (let visited = 0; visited < MAX_LISTED_FOLDERS; visited++) {
|
|
95
|
+
const next = pending.pop();
|
|
96
|
+
if (next === undefined)
|
|
97
|
+
return { ok: true, files };
|
|
98
|
+
let entries;
|
|
99
|
+
try {
|
|
100
|
+
entries = await fs.readdir(next.dir, { withFileTypes: true });
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
const code = err.code;
|
|
104
|
+
return { ok: false, reason: `could not read ${next.dir} (${typeof code === 'string' ? code : 'error'})` };
|
|
105
|
+
}
|
|
106
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const rel = next.rel === '' ? entry.name : `${next.rel}/${entry.name}`;
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
if (next.depth + 1 > MAX_LISTED_DEPTH)
|
|
111
|
+
return { ok: false, reason: `${root} is deeper than ${MAX_LISTED_DEPTH} levels` };
|
|
112
|
+
pending.push({ dir: path.join(next.dir, entry.name), rel, depth: next.depth + 1 });
|
|
113
|
+
}
|
|
114
|
+
else if ((entry.isFile() || entry.isSymbolicLink()) && entry.name !== IGNORED_FILE_NAME) {
|
|
115
|
+
if (files.length >= MAX_LISTED_FILES)
|
|
116
|
+
return { ok: false, reason: `${root} holds more than ${MAX_LISTED_FILES} files` };
|
|
117
|
+
files.push(rel);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return pending.length === 0
|
|
122
|
+
? { ok: true, files }
|
|
123
|
+
: { ok: false, reason: `${root} holds more than ${MAX_LISTED_FOLDERS} folders` };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build the shipped set from the registries, the package skill source and the skill shadows.
|
|
127
|
+
*
|
|
128
|
+
* Every registry skill is listed from `skillsDir(packageRoot)`. A skill whose source is missing is
|
|
129
|
+
* a failed load, not a smaller set: the caller then runs no scan, because a set that lacks a
|
|
130
|
+
* shipped file would send that file to the backup folder.
|
|
131
|
+
*/
|
|
132
|
+
export async function loadShippedSet(options) {
|
|
133
|
+
const skills = getAllSkillNames(new Set());
|
|
134
|
+
const packageSkillFiles = new Map();
|
|
135
|
+
for (const skill of skills) {
|
|
136
|
+
const listed = await listFiles(path.join(skillsDir(options.packageRoot), skill));
|
|
137
|
+
if (!listed.ok)
|
|
138
|
+
return { ok: false, reason: `skill "${skill}": ${listed.reason}` };
|
|
139
|
+
packageSkillFiles.set(skill, listed.files);
|
|
140
|
+
}
|
|
141
|
+
let shadowListings = options.shadowListings;
|
|
142
|
+
if (shadowListings === undefined) {
|
|
143
|
+
const found = new Map();
|
|
144
|
+
for (const skill of skills) {
|
|
145
|
+
const shadowDir = path.join(options.devflowDir, 'skills', skill);
|
|
146
|
+
if ((await validateSkillShadow(shadowDir)) !== 'valid')
|
|
147
|
+
continue;
|
|
148
|
+
const listed = await listFiles(shadowDir);
|
|
149
|
+
if (!listed.ok)
|
|
150
|
+
return { ok: false, reason: `shadow of skill "${skill}": ${listed.reason}` };
|
|
151
|
+
found.set(skill, listed.files);
|
|
152
|
+
}
|
|
153
|
+
shadowListings = found;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
shipped: buildShippedSet({ packageSkillFiles, shadowListings, workflowFolders: options.workflowFolders }),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=shipped-set.js.map
|
|
@@ -15,6 +15,7 @@ import { stampAgentTools } from '../../core/agent-frontmatter.js';
|
|
|
15
15
|
import { agentSourceDirs } from '../../core/assets.js';
|
|
16
16
|
import { mdFileName } from '../../core/orphan-sweep.js';
|
|
17
17
|
import { TRACKER_MCP_SERVERS_DEFAULT, trackerServerTools } from '../../core/tracker.js';
|
|
18
|
+
import { newLinkReport, refuseUnrealFolder, warnLinkReport, writeFileReplacingLink } from './linked-folder.js';
|
|
18
19
|
/**
|
|
19
20
|
* The agent this module owns.
|
|
20
21
|
*
|
|
@@ -97,55 +98,67 @@ async function writeWouldChangeNothing(wanted, target) {
|
|
|
97
98
|
* nothing reports `unchanged` and the summary stays quiet
|
|
98
99
|
* (see {@link writeWouldChangeNothing}).
|
|
99
100
|
*
|
|
101
|
+
* An `agents/devflow` that is a link, or not a folder, is refused: nothing is written there, and the
|
|
102
|
+
* folder is named in `refusedFolders` and in a warning. A `tracker.md` that is a link is replaced by a
|
|
103
|
+
* real file, and named in `replacedLinks` and in a warning.
|
|
104
|
+
*
|
|
100
105
|
* Never throws. A caller reads `converged`; it does not catch. The one refusal
|
|
101
106
|
* that is not an I/O degradation — a claudeDir that is not absolute — is reported
|
|
102
107
|
* the same way rather than thrown, because an install must not die on it.
|
|
103
108
|
*/
|
|
104
109
|
export async function convergeTrackerArtifacts(opts) {
|
|
105
110
|
const { claudeDir, warn } = opts;
|
|
111
|
+
const links = newLinkReport();
|
|
112
|
+
const finish = (converged, agent) => {
|
|
113
|
+
warnLinkReport(links, warn);
|
|
114
|
+
return { converged, agent, replacedLinks: links.replacedLinks, refusedFolders: links.refusedFolders };
|
|
115
|
+
};
|
|
106
116
|
// Precondition, asserted in production code rather than only in tests: an
|
|
107
117
|
// empty or relative claudeDir would make the target resolve somewhere
|
|
108
118
|
// unexpected.
|
|
109
119
|
if (!path.isAbsolute(claudeDir)) {
|
|
110
120
|
warn(`tracker: claudeDir is not an absolute path ("${claudeDir}") — skipping convergence`);
|
|
111
|
-
return
|
|
121
|
+
return finish(false, 'unchanged');
|
|
112
122
|
}
|
|
113
123
|
const target = agentTarget(claudeDir);
|
|
124
|
+
// A link at the folder sends the write into another place, so the folder is proven first.
|
|
125
|
+
if (await refuseUnrealFolder(links, path.dirname(target)))
|
|
126
|
+
return finish(false, 'unchanged');
|
|
114
127
|
const dirs = opts.agentSourceDirs ?? agentSourceDirs();
|
|
115
128
|
const candidates = dirs.map(dir => path.join(dir, mdFileName(TRACKER_AGENT_NAME)));
|
|
116
129
|
const source = await firstExisting(candidates);
|
|
117
130
|
if (source === undefined) {
|
|
118
131
|
warn(`tracker: agent source not found for "${TRACKER_AGENT_NAME}" (searched: ${candidates.join(', ')}) — ` +
|
|
119
132
|
`run \`npm run build:mds\` if it is compiled from an .mds generator host`);
|
|
120
|
-
return
|
|
133
|
+
return finish(false, 'unchanged');
|
|
121
134
|
}
|
|
122
135
|
let wanted;
|
|
123
136
|
try {
|
|
124
137
|
const stamped = stampAgentTools(await fs.readFile(source, 'utf-8'), trackerServerTools(opts.servers ?? TRACKER_MCP_SERVERS_DEFAULT));
|
|
125
138
|
if (!stamped.ok) {
|
|
126
139
|
warn(`tracker: the Tracker agent source could not take the tracker servers (${stamped.error}) — not installed`);
|
|
127
|
-
return
|
|
140
|
+
return finish(false, 'unchanged');
|
|
128
141
|
}
|
|
129
142
|
wanted = stamped.value.content;
|
|
130
143
|
}
|
|
131
144
|
catch (err) {
|
|
132
145
|
warn(`tracker: cannot read the Tracker agent source (${source}) — ${String(err)}`);
|
|
133
|
-
return
|
|
146
|
+
return finish(false, 'unchanged');
|
|
134
147
|
}
|
|
135
148
|
// Already converged — nothing to write, and nothing for the summary to announce.
|
|
136
149
|
// The directory is not created either: an identical file at the target means it is
|
|
137
150
|
// already there (see {@link writeWouldChangeNothing}).
|
|
138
151
|
if (await writeWouldChangeNothing(wanted, target)) {
|
|
139
|
-
return
|
|
152
|
+
return finish(true, 'unchanged');
|
|
140
153
|
}
|
|
141
154
|
try {
|
|
142
155
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
143
|
-
await
|
|
156
|
+
await writeFileReplacingLink(target, wanted, links);
|
|
144
157
|
}
|
|
145
158
|
catch (err) {
|
|
146
159
|
warn(`tracker: failed to install the Tracker agent (${target}) — ${String(err)}`);
|
|
147
|
-
return
|
|
160
|
+
return finish(false, 'unchanged');
|
|
148
161
|
}
|
|
149
|
-
return
|
|
162
|
+
return finish(true, 'installed');
|
|
150
163
|
}
|
|
151
164
|
//# sourceMappingURL=tracker-install.js.map
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* a `--plugin` run that does not install Git leaves the installed, stamped copy alone.
|
|
18
18
|
* 2. `convergeTrackerArtifacts` stamps Tracker as it writes it, and compares the STAMPED text
|
|
19
19
|
* with the installed file, so a steady-state run reports `unchanged`.
|
|
20
|
-
* 3. `
|
|
20
|
+
* 3. `convergeVariantAxes` (init, `devflow curation` and `devflow compliance`) carries the
|
|
21
21
|
* installed stamp onto the variant it writes (`carryStampedTools`), the way it carries
|
|
22
22
|
* `model:` and `effort:`, so a variant switch keeps the list and a repeat run is byte-equal.
|
|
23
23
|
* 4. `devflow tracker --servers` re-stamps the installed copies at once ({@link restampTrackerAgents}).
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
import { promises as fs } from 'fs';
|
|
35
35
|
import * as path from 'path';
|
|
36
36
|
import { stampAgentTools } from '../../core/agent-frontmatter.js';
|
|
37
|
-
import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
|
|
38
37
|
import { mdFileName } from '../../core/orphan-sweep.js';
|
|
39
38
|
import { trackerServerTools } from '../../core/tracker.js';
|
|
39
|
+
import { newLinkReport, refuseUnrealFolder, warnLinkReport, writeFileReplacingLink } from './linked-folder.js';
|
|
40
40
|
import { TRACKER_AGENT_NAME } from './tracker-install.js';
|
|
41
41
|
/** The registry name of the Git agent. */
|
|
42
42
|
export const GIT_AGENT_NAME = 'git';
|
|
@@ -51,7 +51,10 @@ export function isTrackerStampedAgent(agentName) {
|
|
|
51
51
|
*
|
|
52
52
|
* Rewrites only an installed copy (an absent one stays absent), compares against the bytes on
|
|
53
53
|
* disk at the moment of writing, and writes atomically only when they differ, so a run that
|
|
54
|
-
* changes nothing writes nothing.
|
|
54
|
+
* changes nothing writes nothing. An `agents/devflow` that is a link, or not a folder, is refused:
|
|
55
|
+
* nothing is written there, both agents are `failed`, and the folder is named in `refusedFolders` and
|
|
56
|
+
* in a warning. An agent file that is a link is replaced by a real file, and named in `replacedLinks`
|
|
57
|
+
* and in a warning. Never throws.
|
|
55
58
|
*/
|
|
56
59
|
export async function restampTrackerAgents(opts) {
|
|
57
60
|
const { claudeDir, servers, warn } = opts;
|
|
@@ -59,11 +62,22 @@ export async function restampTrackerAgents(opts) {
|
|
|
59
62
|
const unchanged = [];
|
|
60
63
|
const absent = [];
|
|
61
64
|
const failed = [];
|
|
65
|
+
const links = newLinkReport();
|
|
66
|
+
const finish = () => {
|
|
67
|
+
warnLinkReport(links, warn);
|
|
68
|
+
return { rewritten, unchanged, absent, failed, replacedLinks: links.replacedLinks, refusedFolders: links.refusedFolders };
|
|
69
|
+
};
|
|
62
70
|
// Precondition, asserted in production code: a relative claudeDir would resolve the targets
|
|
63
71
|
// against the working directory.
|
|
64
72
|
if (!path.isAbsolute(claudeDir)) {
|
|
65
73
|
warn(`tracker servers: claudeDir is not an absolute path ("${claudeDir}") — the installed agents were not stamped`);
|
|
66
|
-
|
|
74
|
+
failed.push(...TRACKER_STAMPED_AGENTS);
|
|
75
|
+
return finish();
|
|
76
|
+
}
|
|
77
|
+
// A link at the folder sends every write below it into another place, so the folder is proven first.
|
|
78
|
+
if (await refuseUnrealFolder(links, path.join(claudeDir, 'agents', 'devflow'))) {
|
|
79
|
+
failed.push(...TRACKER_STAMPED_AGENTS);
|
|
80
|
+
return finish();
|
|
67
81
|
}
|
|
68
82
|
const tools = trackerServerTools(servers);
|
|
69
83
|
for (const agent of TRACKER_STAMPED_AGENTS) {
|
|
@@ -94,7 +108,7 @@ export async function restampTrackerAgents(opts) {
|
|
|
94
108
|
continue;
|
|
95
109
|
}
|
|
96
110
|
try {
|
|
97
|
-
await
|
|
111
|
+
await writeFileReplacingLink(target, stamped.value.content, links);
|
|
98
112
|
rewritten.push(agent);
|
|
99
113
|
}
|
|
100
114
|
catch (err) {
|
|
@@ -102,6 +116,6 @@ export async function restampTrackerAgents(opts) {
|
|
|
102
116
|
failed.push(agent);
|
|
103
117
|
}
|
|
104
118
|
}
|
|
105
|
-
return
|
|
119
|
+
return finish();
|
|
106
120
|
}
|
|
107
121
|
//# sourceMappingURL=tracker-servers.js.map
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install of a workflow skill folder: `skills/devflow:<name>/`, from the build tree of the
|
|
3
|
+
* machine's exact variant key.
|
|
4
|
+
*
|
|
5
|
+
* D-WORKFLOW-FOLDER-INSTALL: the install (installViaFileCopy) and the converge
|
|
6
|
+
* (convergeVariantAxes, for init, `devflow curation` and `devflow compliance`) both call
|
|
7
|
+
* {@link installWorkflowFolder} after {@link readWorkflowFolder}, so a folder the install writes
|
|
8
|
+
* and a folder the converge writes after a switch are the same bytes. Three rules hold:
|
|
9
|
+
*
|
|
10
|
+
* - ONE source root: the tree built for exactly the machine's two switches
|
|
11
|
+
* (workflowFoldersSourceDir). A key whose tree lacks the folder, or lacks a file of its own
|
|
12
|
+
* file list, is a failed read. Nothing falls through to another key, so a compliance-off
|
|
13
|
+
* machine never receives the text of the compliance-on tree.
|
|
14
|
+
* - EXACTLY the key's file list (workflowFolderFiles): the installed folder holds those files
|
|
15
|
+
* and no others. A file the list omits for the key, and any other file below the folder, is
|
|
16
|
+
* pruned by the swap, so a switch to compliance off leaves no file the compliance-off list omits.
|
|
17
|
+
* - ONE swap: the folder is staged beside the skills folder and moved into place, and the old
|
|
18
|
+
* folder is restored if the move fails. If the restore fails too, the old folder is kept where
|
|
19
|
+
* it was moved to and the result names that path (`recoveryPath`); nothing deletes a backup
|
|
20
|
+
* that the result names as the way back. A folder that already holds the exact files is not
|
|
21
|
+
* touched, so an install that changes nothing writes nothing.
|
|
22
|
+
* - NO LOSS of the user's files: before any rename, the backup session moves every file of the
|
|
23
|
+
* old folder that devflow did not ship (the files of all four variant keys count as shipped)
|
|
24
|
+
* to the backup folder. If one cannot move, the result is `failed` and the folder stays as it
|
|
25
|
+
* is. After the swap the displaced folder is scanned again, its shipped files are removed by the
|
|
26
|
+
* session's list, and it goes with a plain `rmdir`; no folder is removed with a recursive `rm`
|
|
27
|
+
* except the staging folder, which holds only the copies this call wrote.
|
|
28
|
+
*
|
|
29
|
+
* A link at the skills folder or at the folder, or anything there that is not a folder, is refused
|
|
30
|
+
* and recorded in `links.refusedFolders`; a link is never followed and never written through.
|
|
31
|
+
* The folder name is checked at both entry points (the read and the install) with the output-name
|
|
32
|
+
* rule of the registry, because it is joined onto paths, one of them removed recursively.
|
|
33
|
+
* Nothing here throws: a failure is a result with a reason.
|
|
34
|
+
*/
|
|
35
|
+
import { promises as fs } from 'fs';
|
|
36
|
+
import * as path from 'path';
|
|
37
|
+
import { workflowFoldersSourceDir } from '../../core/assets.js';
|
|
38
|
+
import { describeBuildIdCheckError, describeWorkflowFolderError, validateOutputName, verifyFolderBuildId, workflowFolderFiles, workflowFolderKey, } from '../../core/mds-variants.js';
|
|
39
|
+
import { isContainedIn } from '../../core/paths.js';
|
|
40
|
+
import { prefixSkillName } from '../../core/plugins.js';
|
|
41
|
+
import { discardDisplacedFolder } from './folder-swap.js';
|
|
42
|
+
import { formatCappedList } from './install-backup.js';
|
|
43
|
+
import { directoryState, refuseUnrealFolder } from './linked-folder.js';
|
|
44
|
+
/** The deepest level of a folder that is read or compared: `references/<file>` is level 1. */
|
|
45
|
+
const MAX_FOLDER_DEPTH = 4;
|
|
46
|
+
/** The folder inside the staging folder that is moved into place. */
|
|
47
|
+
const STAGED_FOLDER = 'folder';
|
|
48
|
+
/** Where the old folder waits while the new one is moved in: beside its staging folder. */
|
|
49
|
+
export const backupOf = (staging) => `${staging}.old`;
|
|
50
|
+
/** The prefix of the staging folder of one folder install; `mkdtemp` adds six random characters. */
|
|
51
|
+
export const stagingPrefixOf = (claudeDir, name) => path.join(claudeDir, `.devflow-workflow-${name}-${process.pid}-`);
|
|
52
|
+
const errorMessage = (err) => (err instanceof Error ? err.message : String(err));
|
|
53
|
+
/**
|
|
54
|
+
* Read the files one folder holds under the exact variant key of `switches`.
|
|
55
|
+
*
|
|
56
|
+
* Fails, with a reason that names the folder, when its name is not a plain folder name, when the
|
|
57
|
+
* key's tree lacks the folder, when the folder is a link or not a folder, when a file of the key's
|
|
58
|
+
* list is missing or is not a plain file, when the stamped files do not carry one build id (a build
|
|
59
|
+
* that stopped half-way), or when that id is not the id of the files as read (a file from another
|
|
60
|
+
* build, or one changed after the build). The id is recomputed over every file of the key's list.
|
|
61
|
+
*
|
|
62
|
+
* @param root - Package root the build tree is read from.
|
|
63
|
+
*/
|
|
64
|
+
export async function readWorkflowFolder(folder, switches, root) {
|
|
65
|
+
const named = validateOutputName(folder.name);
|
|
66
|
+
if (!named.ok) {
|
|
67
|
+
return { ok: false, reason: describeWorkflowFolderError({ kind: 'bad-folder-name', folder: folder.name, error: named.error }) };
|
|
68
|
+
}
|
|
69
|
+
const key = workflowFolderKey(switches);
|
|
70
|
+
const dir = path.join(workflowFoldersSourceDir(switches, root), folder.name);
|
|
71
|
+
if ((await directoryState(dir)) !== 'real') {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
reason: `workflow folder '${folder.name}' is not built for the variant key '${key}' (${dir}). Ensure build:mds ran successfully before install.`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const files = [];
|
|
78
|
+
for (const rel of workflowFolderFiles(folder, key)) {
|
|
79
|
+
if (!isContainedIn(dir, rel)) {
|
|
80
|
+
return { ok: false, reason: `workflow folder '${folder.name}' names the file '${rel}', which leaves the folder` };
|
|
81
|
+
}
|
|
82
|
+
const source = path.join(dir, ...rel.split('/'));
|
|
83
|
+
try {
|
|
84
|
+
if (!(await fs.lstat(source)).isFile()) {
|
|
85
|
+
return { ok: false, reason: `${source} is not a plain file` };
|
|
86
|
+
}
|
|
87
|
+
files.push({ file: rel, bytes: await fs.readFile(source) });
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return { ok: false, reason: `${source} could not be read (${errorMessage(err)})` };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const ids = verifyFolderBuildId(files.map(({ file, bytes }) => ({ file, body: bytes.toString('utf-8') })));
|
|
94
|
+
if (!ids.ok) {
|
|
95
|
+
return { ok: false, reason: `workflow folder '${folder.name}' (${key}): ${describeBuildIdCheckError(ids.error)}` };
|
|
96
|
+
}
|
|
97
|
+
return { ok: true, files };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Every entry below `dir` as sorted POSIX paths: a file as `a/b.md`, a folder as `a/`. Null when
|
|
101
|
+
* anything is neither a plain file nor a folder (a link, a socket), or the walk is deeper than the bound.
|
|
102
|
+
*/
|
|
103
|
+
async function listEntries(dir, prefix = '', depth = 0) {
|
|
104
|
+
if (depth > MAX_FOLDER_DEPTH)
|
|
105
|
+
return null;
|
|
106
|
+
let entries;
|
|
107
|
+
try {
|
|
108
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
const out = [];
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const rel = `${prefix}${entry.name}`;
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
const nested = await listEntries(path.join(dir, entry.name), `${rel}/`, depth + 1);
|
|
118
|
+
if (nested === null)
|
|
119
|
+
return null;
|
|
120
|
+
out.push(`${rel}/`, ...nested);
|
|
121
|
+
}
|
|
122
|
+
else if (entry.isFile()) {
|
|
123
|
+
out.push(rel);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out.sort();
|
|
130
|
+
}
|
|
131
|
+
/** The entries a folder of exactly these files holds: the files and the folders that hold them. */
|
|
132
|
+
function expectedEntries(files) {
|
|
133
|
+
const entries = new Set();
|
|
134
|
+
for (const { file } of files) {
|
|
135
|
+
entries.add(file);
|
|
136
|
+
const parts = file.split('/');
|
|
137
|
+
for (let i = 1; i < parts.length; i++)
|
|
138
|
+
entries.add(`${parts.slice(0, i).join('/')}/`);
|
|
139
|
+
}
|
|
140
|
+
return [...entries].sort();
|
|
141
|
+
}
|
|
142
|
+
/** True only when `target` holds exactly these files with exactly these bytes. Any error answers false. */
|
|
143
|
+
async function folderHolds(target, files) {
|
|
144
|
+
const listed = await listEntries(target);
|
|
145
|
+
const wanted = expectedEntries(files);
|
|
146
|
+
if (listed === null || listed.length !== wanted.length || listed.some((entry, i) => entry !== wanted[i]))
|
|
147
|
+
return false;
|
|
148
|
+
for (const { file, bytes } of files) {
|
|
149
|
+
try {
|
|
150
|
+
if (!bytes.equals(await fs.readFile(path.join(target, ...file.split('/')))))
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Write `skills/devflow:<name>/` as exactly `files`: move the user's files aside, stage, then swap
|
|
161
|
+
* in. See the module comment.
|
|
162
|
+
*
|
|
163
|
+
* The staging folder sits under `claudeDir`, not under `skills/`, so a folder a crash leaves
|
|
164
|
+
* behind never looks like a second skill to Claude Code. It is made by `mkdtemp`, an exclusive
|
|
165
|
+
* create, so nothing that stands at a guessed name is written through; its name carries the
|
|
166
|
+
* unit kind, the folder name and the process id, and `mkdtemp` adds the random token. The folder
|
|
167
|
+
* is built in a folder inside it, which keeps the default mode of a folder this process makes.
|
|
168
|
+
* Never throws.
|
|
169
|
+
*/
|
|
170
|
+
export async function installWorkflowFolder(opts) {
|
|
171
|
+
const { claudeDir, name, files, links, backups } = opts;
|
|
172
|
+
if (!path.isAbsolute(claudeDir)) {
|
|
173
|
+
return { name, outcome: 'failed', reason: `claudeDir is not an absolute path ("${claudeDir}")` };
|
|
174
|
+
}
|
|
175
|
+
const named = validateOutputName(name);
|
|
176
|
+
if (!named.ok) {
|
|
177
|
+
return { name, outcome: 'failed', reason: describeWorkflowFolderError({ kind: 'bad-folder-name', folder: name, error: named.error }) };
|
|
178
|
+
}
|
|
179
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
180
|
+
const target = path.join(skillsDir, prefixSkillName(name));
|
|
181
|
+
const escaping = files.find(({ file }) => !isContainedIn(target, file));
|
|
182
|
+
if (escaping !== undefined) {
|
|
183
|
+
return { name, outcome: 'failed', reason: `workflow folder '${name}' names the file '${escaping.file}', which leaves the folder` };
|
|
184
|
+
}
|
|
185
|
+
if (await refuseUnrealFolder(links, skillsDir))
|
|
186
|
+
return { name, outcome: 'refused' };
|
|
187
|
+
if (await refuseUnrealFolder(links, target))
|
|
188
|
+
return { name, outcome: 'refused' };
|
|
189
|
+
let staging = null;
|
|
190
|
+
try {
|
|
191
|
+
if ((await directoryState(target)) === 'real') {
|
|
192
|
+
// Before any rename: what devflow did not ship moves out of the folder, whichever key's files it holds.
|
|
193
|
+
await backups.prepass([target]);
|
|
194
|
+
const kept = backups.keptUnder(target);
|
|
195
|
+
if (kept.length > 0) {
|
|
196
|
+
return {
|
|
197
|
+
name,
|
|
198
|
+
outcome: 'failed',
|
|
199
|
+
reason: `${kept.length} file(s) in ${target} could not be moved aside, so the folder was left as it is: ${formatCappedList(kept)}`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (await folderHolds(target, files))
|
|
203
|
+
return { name, outcome: 'unchanged' };
|
|
204
|
+
}
|
|
205
|
+
await fs.mkdir(claudeDir, { recursive: true });
|
|
206
|
+
staging = await fs.mkdtemp(stagingPrefixOf(claudeDir, name));
|
|
207
|
+
const staged = path.join(staging, STAGED_FOLDER);
|
|
208
|
+
await fs.mkdir(staged);
|
|
209
|
+
for (const { file, bytes } of files) {
|
|
210
|
+
const stagedFile = path.join(staged, ...file.split('/'));
|
|
211
|
+
await fs.mkdir(path.dirname(stagedFile), { recursive: true });
|
|
212
|
+
await fs.writeFile(stagedFile, bytes);
|
|
213
|
+
}
|
|
214
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
215
|
+
const hadTarget = (await directoryState(target)) === 'real';
|
|
216
|
+
const backup = backupOf(staging);
|
|
217
|
+
if (hadTarget)
|
|
218
|
+
await fs.rename(target, backup);
|
|
219
|
+
try {
|
|
220
|
+
await fs.rename(staged, target);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
if (!hadTarget)
|
|
224
|
+
throw err;
|
|
225
|
+
try {
|
|
226
|
+
await fs.rename(backup, target);
|
|
227
|
+
}
|
|
228
|
+
catch (restoreErr) {
|
|
229
|
+
return {
|
|
230
|
+
name,
|
|
231
|
+
outcome: 'failed',
|
|
232
|
+
reason: `${errorMessage(err)}; the previous folder could not be put back (${errorMessage(restoreErr)}) and is kept at ${backup}`,
|
|
233
|
+
recoveryPath: backup,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
// The displaced folder is scanned again, because a file can arrive after the first scan.
|
|
239
|
+
if (hadTarget)
|
|
240
|
+
await discardDisplacedFolder(backups, backup, target);
|
|
241
|
+
return { name, outcome: 'installed' };
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
return { name, outcome: 'failed', reason: errorMessage(err) };
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
if (staging !== null)
|
|
248
|
+
await fs.rm(staging, { recursive: true, force: true }).catch(() => undefined);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=workflow-folder-install.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"hosts": {
|
|
4
|
+
"agents/code": {
|
|
5
|
+
"curation": true,
|
|
6
|
+
"compliance": false
|
|
7
|
+
},
|
|
8
|
+
"agents/debug": {
|
|
9
|
+
"curation": true,
|
|
10
|
+
"compliance": false
|
|
11
|
+
},
|
|
12
|
+
"agents/design": {
|
|
13
|
+
"curation": true,
|
|
14
|
+
"compliance": false
|
|
15
|
+
},
|
|
16
|
+
"agents/fix": {
|
|
17
|
+
"curation": true,
|
|
18
|
+
"compliance": false
|
|
19
|
+
},
|
|
20
|
+
"agents/git": {
|
|
21
|
+
"curation": false,
|
|
22
|
+
"compliance": true
|
|
23
|
+
},
|
|
24
|
+
"agents/harden": {
|
|
25
|
+
"curation": true,
|
|
26
|
+
"compliance": false
|
|
27
|
+
},
|
|
28
|
+
"agents/knowledge": {
|
|
29
|
+
"curation": true,
|
|
30
|
+
"compliance": false
|
|
31
|
+
},
|
|
32
|
+
"agents/refactor": {
|
|
33
|
+
"curation": true,
|
|
34
|
+
"compliance": false
|
|
35
|
+
},
|
|
36
|
+
"agents/research": {
|
|
37
|
+
"curation": true,
|
|
38
|
+
"compliance": false
|
|
39
|
+
},
|
|
40
|
+
"agents/review": {
|
|
41
|
+
"curation": true,
|
|
42
|
+
"compliance": false
|
|
43
|
+
},
|
|
44
|
+
"agents/skim": {
|
|
45
|
+
"curation": true,
|
|
46
|
+
"compliance": false
|
|
47
|
+
},
|
|
48
|
+
"agents/triage": {
|
|
49
|
+
"curation": true,
|
|
50
|
+
"compliance": true
|
|
51
|
+
},
|
|
52
|
+
"commands/bug-analysis": {
|
|
53
|
+
"curation": true,
|
|
54
|
+
"compliance": true
|
|
55
|
+
},
|
|
56
|
+
"commands/code-review": {
|
|
57
|
+
"curation": true,
|
|
58
|
+
"compliance": true
|
|
59
|
+
},
|
|
60
|
+
"commands/debug": {
|
|
61
|
+
"curation": true,
|
|
62
|
+
"compliance": false
|
|
63
|
+
},
|
|
64
|
+
"commands/dynamic-build": {
|
|
65
|
+
"curation": true,
|
|
66
|
+
"compliance": true
|
|
67
|
+
},
|
|
68
|
+
"commands/dynamic-plan": {
|
|
69
|
+
"curation": true,
|
|
70
|
+
"compliance": false
|
|
71
|
+
},
|
|
72
|
+
"commands/dynamic-profile": {
|
|
73
|
+
"curation": true,
|
|
74
|
+
"compliance": false
|
|
75
|
+
},
|
|
76
|
+
"commands/dynamic-tickets": {
|
|
77
|
+
"curation": true,
|
|
78
|
+
"compliance": true
|
|
79
|
+
},
|
|
80
|
+
"commands/explore": {
|
|
81
|
+
"curation": true,
|
|
82
|
+
"compliance": false
|
|
83
|
+
},
|
|
84
|
+
"commands/implement": {
|
|
85
|
+
"curation": true,
|
|
86
|
+
"compliance": true
|
|
87
|
+
},
|
|
88
|
+
"commands/plan": {
|
|
89
|
+
"curation": true,
|
|
90
|
+
"compliance": true
|
|
91
|
+
},
|
|
92
|
+
"commands/release": {
|
|
93
|
+
"curation": true,
|
|
94
|
+
"compliance": true
|
|
95
|
+
},
|
|
96
|
+
"commands/research": {
|
|
97
|
+
"curation": true,
|
|
98
|
+
"compliance": false
|
|
99
|
+
},
|
|
100
|
+
"commands/resolve": {
|
|
101
|
+
"curation": true,
|
|
102
|
+
"compliance": true
|
|
103
|
+
},
|
|
104
|
+
"commands/self-review": {
|
|
105
|
+
"curation": true,
|
|
106
|
+
"compliance": false
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|