@chenguangyao/devflow-kit 0.1.43
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 +232 -0
- package/LICENSE +21 -0
- package/README.md +539 -0
- package/bin/devflow.js +9 -0
- package/docs/RFC-001-devflow-kit.md +617 -0
- package/docs/RFC-002-workflow-kernel.md +134 -0
- package/docs/enterprise-integration-supplement.md +274 -0
- package/docs/internal-gitlab-setup.md +426 -0
- package/docs/marketplace-skills.md +231 -0
- package/docs/migration-from-arb.md +232 -0
- package/docs/tooling-overview.md +774 -0
- package/docs/workflow-orchestration.md +695 -0
- package/docs/workflow-ui-prototype.html +271 -0
- package/package.json +52 -0
- package/schemas/config.schema.json +51 -0
- package/schemas/delta.schema.json +22 -0
- package/schemas/state.schema.json +130 -0
- package/schemas/status-surface.schema.json +197 -0
- package/schemas/workflow-confirmation-surface.schema.json +70 -0
- package/schemas/workflow-picker.schema.json +94 -0
- package/scripts/postinstall.js +101 -0
- package/scripts/render-workflow-ui-prototype.js +271 -0
- package/skills/apply/SKILL.md +313 -0
- package/skills/apply/references/discipline-checklist.md +145 -0
- package/skills/apply/references/subagent-implementer-prompt.md +113 -0
- package/skills/apply/references/subagent-orchestration.md +150 -0
- package/skills/apply/references/subagent-reviewer-prompt.md +180 -0
- package/skills/apply/references/tdd-loop.md +287 -0
- package/skills/apply/references/when-plan-is-wrong.md +279 -0
- package/skills/apply/references/worktree-swarm.md +292 -0
- package/skills/archive/SKILL.md +229 -0
- package/skills/archive/references/conflict-resolution.md +336 -0
- package/skills/archive/references/knowledge-deposit.md +381 -0
- package/skills/archive/references/spec-merge.md +365 -0
- package/skills/brainstorm/SKILL.md +123 -0
- package/skills/brainstorm/references/proposal-template.md +244 -0
- package/skills/brainstorm/references/question-catalog.md +168 -0
- package/skills/brainstorm/references/session-template.md +184 -0
- package/skills/ci-fix/SKILL.md +63 -0
- package/skills/ci-fix/references/loop.md +25 -0
- package/skills/code-review/SKILL.md +279 -0
- package/skills/code-review/references/escalation-playbook.md +192 -0
- package/skills/code-review/references/language-cheatsheets/go.md +175 -0
- package/skills/code-review/references/language-cheatsheets/java-spring-mybatis.md +246 -0
- package/skills/code-review/references/language-cheatsheets/python.md +170 -0
- package/skills/code-review/references/language-cheatsheets/vue.md +199 -0
- package/skills/code-review/references/output-template.md +275 -0
- package/skills/code-review/references/review-checklist.md +251 -0
- package/skills/complexity-grading/SKILL.md +259 -0
- package/skills/deliver/SKILL.md +271 -0
- package/skills/deliver/references/delivery-modes.md +299 -0
- package/skills/deliver/references/notify.md +359 -0
- package/skills/deliver/references/pr-description.md +319 -0
- package/skills/dependency-upgrade/SKILL.md +57 -0
- package/skills/dependency-upgrade/references/risk-matrix.md +38 -0
- package/skills/df-orchestrator/SKILL.md +407 -0
- package/skills/df-orchestrator/references/complexity-grading.md +177 -0
- package/skills/df-orchestrator/references/escalation-matrix.md +191 -0
- package/skills/df-orchestrator/references/routing-rules.md +290 -0
- package/skills/df-orchestrator/references/workflow-state-machine.md +208 -0
- package/skills/frontend-quality/SKILL.md +61 -0
- package/skills/frontend-quality/references/checklist.md +35 -0
- package/skills/handoff-resume/SKILL.md +59 -0
- package/skills/handoff-resume/references/handoff-template.md +54 -0
- package/skills/plan/SKILL.md +166 -0
- package/skills/plan/references/task-breakdown.md +207 -0
- package/skills/plan/references/task-sequencing.md +143 -0
- package/skills/plan/references/task-template.md +248 -0
- package/skills/requirement-analysis/SKILL.md +499 -0
- package/skills/requirement-analysis/references/acceptance-criteria.md +183 -0
- package/skills/requirement-analysis/references/code-recon.md +151 -0
- package/skills/requirement-analysis/references/edge-case-catalog.md +164 -0
- package/skills/requirement-analysis/references/requirement-template.md +339 -0
- package/skills/requirement-analysis/references/scope-negotiation.md +162 -0
- package/skills/security-hardening/SKILL.md +60 -0
- package/skills/security-hardening/references/checklist.md +42 -0
- package/skills/tech-spec/SKILL.md +388 -0
- package/skills/tech-spec/references/api-contract-design.md +172 -0
- package/skills/tech-spec/references/decision-records.md +110 -0
- package/skills/tech-spec/references/design-template.md +301 -0
- package/skills/tech-spec/references/rollout-and-rollback.md +203 -0
- package/skills/tech-spec/references/spec-delta-conventions.md +250 -0
- package/skills/tech-spec/references/transaction-patterns.md +212 -0
- package/skills/test-spec/SKILL.md +219 -0
- package/skills/test-spec/references/coverage-strategy.md +218 -0
- package/skills/test-spec/references/edge-case-to-test.md +143 -0
- package/skills/test-spec/references/test-case-template.md +276 -0
- package/skills/verify/SKILL.md +232 -0
- package/skills/verify/references/nfr-verification.md +292 -0
- package/skills/verify/references/report-templates.md +510 -0
- package/skills/verify/references/self-test-guide.md +240 -0
- package/skills/verify/references/verify-rollback-map.md +247 -0
- package/src/cli/commands/_helpers.js +108 -0
- package/src/cli/commands/_submit.js +718 -0
- package/src/cli/commands/apply.js +198 -0
- package/src/cli/commands/archive.js +180 -0
- package/src/cli/commands/checkpoint.js +113 -0
- package/src/cli/commands/deliver.js +377 -0
- package/src/cli/commands/deploy.js +504 -0
- package/src/cli/commands/design.js +158 -0
- package/src/cli/commands/disable.js +21 -0
- package/src/cli/commands/doctor.js +178 -0
- package/src/cli/commands/enable.js +21 -0
- package/src/cli/commands/flow.js +645 -0
- package/src/cli/commands/help.js +93 -0
- package/src/cli/commands/ingest.js +602 -0
- package/src/cli/commands/init.js +341 -0
- package/src/cli/commands/knowledge.js +523 -0
- package/src/cli/commands/logs.js +43 -0
- package/src/cli/commands/new.js +202 -0
- package/src/cli/commands/plan.js +49 -0
- package/src/cli/commands/propose.js +27 -0
- package/src/cli/commands/provider.js +698 -0
- package/src/cli/commands/report.js +143 -0
- package/src/cli/commands/requirement.js +227 -0
- package/src/cli/commands/review.js +301 -0
- package/src/cli/commands/skills.js +457 -0
- package/src/cli/commands/status.js +925 -0
- package/src/cli/commands/switch.js +27 -0
- package/src/cli/commands/sync.js +47 -0
- package/src/cli/commands/test.js +366 -0
- package/src/cli/commands/uninstall.js +32 -0
- package/src/cli/commands/update.js +74 -0
- package/src/cli/commands/verify.js +354 -0
- package/src/cli/commands/worktree.js +78 -0
- package/src/cli/index.js +72 -0
- package/src/cli/parse-args.js +102 -0
- package/src/core/autodetect.js +271 -0
- package/src/core/change.js +208 -0
- package/src/core/checkpoint.js +217 -0
- package/src/core/config.js +60 -0
- package/src/core/delta.js +290 -0
- package/src/core/markers.js +59 -0
- package/src/core/paths.js +173 -0
- package/src/core/plan-tasks.js +36 -0
- package/src/core/project-routing.js +285 -0
- package/src/core/projects.js +200 -0
- package/src/core/state.js +200 -0
- package/src/core/workflow-check.js +177 -0
- package/src/core/workflow-init.js +34 -0
- package/src/core/workflow-picker.js +154 -0
- package/src/core/workflow-policy.js +119 -0
- package/src/core/workflow-suggest.js +181 -0
- package/src/core/workflow-verify.js +88 -0
- package/src/core/workflow.js +433 -0
- package/src/core/worktree.js +241 -0
- package/src/knowledge/categories.js +107 -0
- package/src/knowledge/classify.js +125 -0
- package/src/knowledge/deposit.js +414 -0
- package/src/knowledge/migrate.js +149 -0
- package/src/knowledge/mr.js +219 -0
- package/src/knowledge/query.js +131 -0
- package/src/knowledge/registry.js +151 -0
- package/src/knowledge/sync.js +179 -0
- package/src/providers/base.js +74 -0
- package/src/providers/drivers/api-yapi.js +78 -0
- package/src/providers/drivers/ci-jenkins.js +109 -0
- package/src/providers/drivers/intake-confluence.js +544 -0
- package/src/providers/drivers/kb-git.js +549 -0
- package/src/providers/drivers/kb-weknora.js +472 -0
- package/src/providers/drivers/notify-smtp.js +515 -0
- package/src/providers/drivers/observability-oss.js +43 -0
- package/src/providers/drivers/observability-sls.js +50 -0
- package/src/providers/lifecycle.js +135 -0
- package/src/providers/loader.js +132 -0
- package/src/providers/local.js +190 -0
- package/src/providers/userconfig.js +283 -0
- package/src/reports/aggregate.js +185 -0
- package/src/reports/coverage.js +163 -0
- package/src/reports/detect.js +143 -0
- package/src/reports/parse.js +236 -0
- package/src/templates/files/ci/github.yml +38 -0
- package/src/templates/files/ci/gitlab.yml +27 -0
- package/src/templates/files/design.md +63 -0
- package/src/templates/files/ide/devflow-workflow.md +58 -0
- package/src/templates/files/ide/project-overview-reference.md +1 -0
- package/src/templates/files/ide/project-overview.md +27 -0
- package/src/templates/files/knowledge-index.json +17 -0
- package/src/templates/files/knowledge.md +28 -0
- package/src/templates/files/meta.json +8 -0
- package/src/templates/files/plan.md +38 -0
- package/src/templates/files/proposal.md +33 -0
- package/src/templates/files/reports/contract-test.md +40 -0
- package/src/templates/files/reports/e2e-test.md +30 -0
- package/src/templates/files/reports/integration-test.md +36 -0
- package/src/templates/files/reports/joint-test.md +58 -0
- package/src/templates/files/reports/perf.md +24 -0
- package/src/templates/files/reports/regression.md +20 -0
- package/src/templates/files/reports/remote-test.md +55 -0
- package/src/templates/files/reports/self-test.md +43 -0
- package/src/templates/files/reports/smoke-test.md +22 -0
- package/src/templates/files/reports/unit-test.md +36 -0
- package/src/templates/files/requirement.md +51 -0
- package/src/templates/files/review.md +38 -0
- package/src/templates/files/tests.md +36 -0
- package/src/templates/files/verify.md +32 -0
- package/src/templates/index.js +21 -0
- package/src/utils/log.js +37 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
const fsSync = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const log = require('../../utils/log.js');
|
|
7
|
+
const paths = require('../../core/paths.js');
|
|
8
|
+
const change = require('../../core/change.js');
|
|
9
|
+
const templates = require('../../templates/index.js');
|
|
10
|
+
const helpers = require('./_helpers.js');
|
|
11
|
+
const depositMod = require('../../knowledge/deposit.js');
|
|
12
|
+
const mrMod = require('../../knowledge/mr.js');
|
|
13
|
+
const sync = require('../../knowledge/sync.js');
|
|
14
|
+
const migrate = require('../../knowledge/migrate.js');
|
|
15
|
+
const query = require('../../knowledge/query.js');
|
|
16
|
+
const loader = require('../../providers/loader.js');
|
|
17
|
+
|
|
18
|
+
async function run({ sub, positional = [], flags = {}, cwd }) {
|
|
19
|
+
const root = cwd || process.cwd();
|
|
20
|
+
const action = sub || positional[0];
|
|
21
|
+
switch (action) {
|
|
22
|
+
case 'init': return initKb(root, flags);
|
|
23
|
+
case 'query': return runQuery(root, positional, flags);
|
|
24
|
+
case 'deposit': return runDeposit(root, flags);
|
|
25
|
+
case 'sync': return runSync(root, flags);
|
|
26
|
+
case 'reparse': return runReparse(root, flags);
|
|
27
|
+
case 'validate': return validate(root, flags);
|
|
28
|
+
case 'migrate': return runMigrate(root, flags);
|
|
29
|
+
case undefined:
|
|
30
|
+
case 'help': return help();
|
|
31
|
+
default:
|
|
32
|
+
log.error(`unknown subcommand: ${action}`);
|
|
33
|
+
help();
|
|
34
|
+
process.exitCode = 2;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function initKb(root, _flags) {
|
|
39
|
+
const dir = paths.workspaceKnowledgeRoot();
|
|
40
|
+
if (!fsSync.existsSync(dir)) {
|
|
41
|
+
log.error('workspace knowledge/ not present. run "devflow init" first.');
|
|
42
|
+
process.exitCode = 1; return;
|
|
43
|
+
}
|
|
44
|
+
log.ok(`workspace knowledge skeleton present: ${dir}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function runQuery(root, args, flags) {
|
|
48
|
+
const q = args.join(' ').trim();
|
|
49
|
+
if (!q) { log.error('usage: devflow knowledge query "<keywords>"'); process.exitCode = 2; return; }
|
|
50
|
+
const keywords = q.split(/\s+/).filter(Boolean);
|
|
51
|
+
const slug = await helpers.resolveSlug(root, flags, []);
|
|
52
|
+
if (!slug) { process.exitCode = 1; return; }
|
|
53
|
+
|
|
54
|
+
const result = await query.query(root, { keywords, slug, k: parseInt(flags.k, 10) || 8, withRemote: !flags.localOnly });
|
|
55
|
+
// Pretty print to stdout.
|
|
56
|
+
log.info(`local hits: ${result.hits.length}, remote hits: ${result.remoteHits.length}`);
|
|
57
|
+
for (const h of result.hits) {
|
|
58
|
+
log.raw(` ${h.score.toFixed(2).padStart(5)} [${h.source || 'knowledge'}] ${h.path}${h.line > 0 ? ':' + h.line : ''}`);
|
|
59
|
+
if (h.snippet) log.dim(' ' + h.snippet);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Persist into changes/<slug>/knowledge.md
|
|
63
|
+
const tpl = await templates.load('knowledge.md');
|
|
64
|
+
const lines = [];
|
|
65
|
+
lines.push('## 命中(本地)');
|
|
66
|
+
lines.push('');
|
|
67
|
+
for (const h of result.hits) {
|
|
68
|
+
lines.push(`- (score=${h.score}) **${h.source || 'knowledge'}** [${h.path}](${path.posix.join('..', '..', '..', h.path.split(path.sep).join('/'))})${h.line > 0 ? ' L' + h.line : ''}`);
|
|
69
|
+
if (h.snippet) lines.push(` > ${h.snippet}`);
|
|
70
|
+
}
|
|
71
|
+
if (result.remoteHits.length) {
|
|
72
|
+
lines.push('');
|
|
73
|
+
lines.push('## 命中(远程)');
|
|
74
|
+
lines.push('');
|
|
75
|
+
for (const h of result.remoteHits) {
|
|
76
|
+
lines.push(`- (score=${h.score ?? '?'}) ${h.title || h.uuid}${h.url ? ` <${h.url}>` : ''}`);
|
|
77
|
+
if (h.snippet) lines.push(` > ${h.snippet}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const body = templates.render(tpl, {
|
|
81
|
+
slug, title: slug, query: q, sources: result.remoteHits.length ? 'local + remote' : 'local',
|
|
82
|
+
date: new Date().toISOString(),
|
|
83
|
+
}) + '\n' + lines.join('\n') + '\n';
|
|
84
|
+
await change.writeArtifact(root, slug, 'knowledge.md', body);
|
|
85
|
+
log.ok(`changes/${slug}/knowledge.md written`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runDeposit(root, flags) {
|
|
89
|
+
const slug = await helpers.resolveSlug(root, flags, []);
|
|
90
|
+
if (!slug) { process.exitCode = 1; return; }
|
|
91
|
+
const dryRun = !!(flags.dryRun || flags['dry-run']);
|
|
92
|
+
const autoYes = !!(flags.yes || flags.y);
|
|
93
|
+
const mrMode = !!(flags.mr || flags.MR);
|
|
94
|
+
|
|
95
|
+
// Step 0 (MR mode): detect kb-git provider and list external KB categories.
|
|
96
|
+
// Mirrors arb_knowledge.py ensure + list-tags before deposit.
|
|
97
|
+
// NOTE: load by name 'kb.git' (or find first kb provider with driver=git) to avoid
|
|
98
|
+
// being shadowed by kb.weknora which shares the same type='kb'.
|
|
99
|
+
let kbGitProvider = null;
|
|
100
|
+
let externalTags = null;
|
|
101
|
+
if (mrMode) {
|
|
102
|
+
try {
|
|
103
|
+
const all = await loader.listConfigured(root);
|
|
104
|
+
const gitEntry = all.find((e) => e.type === 'kb' && e.driver === 'git');
|
|
105
|
+
if (gitEntry) {
|
|
106
|
+
const p = await loader.load(root, { name: gitEntry.name });
|
|
107
|
+
kbGitProvider = p;
|
|
108
|
+
log.info(`检测到外部知识库仓库 (${gitEntry.name}),正在 ensure clone…`);
|
|
109
|
+
await p.ensure();
|
|
110
|
+
externalTags = await p.listTags();
|
|
111
|
+
printExternalTags(externalTags);
|
|
112
|
+
}
|
|
113
|
+
} catch (e) { log.dim(`kb-git not available: ${e.message}`); /* fall through to local mode */ }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Phase 1: compute plan (always dry-run first).
|
|
117
|
+
// --mr implies force: skip sha256 noop check so existing files are always re-written and committed.
|
|
118
|
+
const plan = await depositMod.deposit(root, { slug, dryRun: true, force: mrMode });
|
|
119
|
+
if (!plan.length) {
|
|
120
|
+
log.warn('nothing to deposit (no knowledge/ files, design.md, or incident reports?)');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Phase 2: render plan table.
|
|
125
|
+
const actionable = plan.filter((p) => p.action !== 'noop');
|
|
126
|
+
const noops = plan.filter((p) => p.action === 'noop');
|
|
127
|
+
printDepositTable(plan, { mrMode, hasExternal: !!kbGitProvider });
|
|
128
|
+
|
|
129
|
+
if (dryRun) {
|
|
130
|
+
if (mrMode) {
|
|
131
|
+
if (kbGitProvider) {
|
|
132
|
+
const mrDry = await kbGitProvider.depositAndCreateMr(root, { items: actionable, slug, dryRun: true });
|
|
133
|
+
log.info(`[dry-run] 外部仓库 MR mode: ${mrDry.message}`);
|
|
134
|
+
} else {
|
|
135
|
+
const mrDry = await mrMod.createKnowledgePr(root, { slug, items: actionable, dryRun: true });
|
|
136
|
+
log.info(`[dry-run] MR mode: ${mrDry.message || `branch ${mrDry.branch} → ${mrDry.baseBranch}`}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
log.ok(`[dry-run] ${actionable.length} item(s) would be written, ${noops.length} unchanged.`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!actionable.length) {
|
|
143
|
+
log.ok('all items unchanged (noop).');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Phase 3: confirm (skip if --yes).
|
|
148
|
+
let selected = actionable;
|
|
149
|
+
if (!autoYes) {
|
|
150
|
+
selected = await interactiveConfirm(actionable, { mrMode });
|
|
151
|
+
if (!selected) { log.warn('deposit skipped.'); return; }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Phase 4: write knowledge files locally.
|
|
155
|
+
await depositMod.executeItems(root, selected, slug);
|
|
156
|
+
const created = selected.filter((p) => p.action === 'create').length;
|
|
157
|
+
const updated = selected.filter((p) => p.action === 'update').length;
|
|
158
|
+
log.ok(`deposit done: create=${created} update=${updated} (${noops.length} unchanged)`);
|
|
159
|
+
|
|
160
|
+
// Phase 5 (MR mode): create branch + PR/MR.
|
|
161
|
+
if (mrMode) {
|
|
162
|
+
log.info('creating knowledge branch + PR/MR…');
|
|
163
|
+
try {
|
|
164
|
+
let mr;
|
|
165
|
+
if (kbGitProvider) {
|
|
166
|
+
// External repo mode (arb-knowledge pattern).
|
|
167
|
+
mr = await kbGitProvider.depositAndCreateMr(root, { items: selected, slug });
|
|
168
|
+
mr.mode = 'external';
|
|
169
|
+
} else {
|
|
170
|
+
mr = await mrMod.createKnowledgePr(root, { slug, items: selected });
|
|
171
|
+
}
|
|
172
|
+
_printMrResult(mr);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
log.error(`MR creation failed: ${e.message}`);
|
|
175
|
+
log.dim(' knowledge files were written locally — commit and push manually if needed.');
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function _printMrResult(mr) {
|
|
182
|
+
const repoDesc = mr.mode === 'external' ? '外部知识库仓库' : '当前仓库';
|
|
183
|
+
switch (mr.status) {
|
|
184
|
+
case 'created':
|
|
185
|
+
log.ok(`PR/MR created (${repoDesc})`);
|
|
186
|
+
if (mr.url) log.raw(` MR: ${mr.url}`);
|
|
187
|
+
if (mr.branch) log.dim(` branch: ${mr.branch}${mr.baseBranch ? ' → ' + mr.baseBranch : ''}`);
|
|
188
|
+
if (mr.commit_hash) log.dim(` commit: ${mr.commit_hash.slice(0, 8)}`);
|
|
189
|
+
log.raw('');
|
|
190
|
+
log.info('下一步:');
|
|
191
|
+
log.raw(' • 请在 Git 平台上完成 MR Review(内容审核 + 合并)');
|
|
192
|
+
if (mr.mode === 'external') {
|
|
193
|
+
log.raw(' • 合并后 CI 自动同步到 WeKnora(新增条目 ID 由 CI 回写至 .meta.json)');
|
|
194
|
+
}
|
|
195
|
+
log.raw(' • 本 skill 到此结束,不等待 MR 合并');
|
|
196
|
+
break;
|
|
197
|
+
case 'pushed_no_mr':
|
|
198
|
+
log.warn(`branch pushed (${repoDesc}) but PR/MR creation failed: ${mr.error}`);
|
|
199
|
+
log.dim(` branch ${mr.branch} pushed to origin — create the MR manually.`);
|
|
200
|
+
break;
|
|
201
|
+
case 'no_changes':
|
|
202
|
+
log.warn('no knowledge changes detected by git after deposit (already committed?)');
|
|
203
|
+
break;
|
|
204
|
+
case 'workspace_requires_kb_git':
|
|
205
|
+
log.error(mr.error || 'workspace knowledge MR requires kb.git provider');
|
|
206
|
+
log.dim(' 请配置 kb.git,让 MR 提到外部知识库项目。');
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
break;
|
|
209
|
+
default:
|
|
210
|
+
log.info(`status: ${mr.status}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Print the external KB repo's category list (mirrors arb_knowledge.py list-tags output).
|
|
216
|
+
* Helps AI / user know which directories exist and where to classify new knowledge.
|
|
217
|
+
*/
|
|
218
|
+
function printExternalTags(tags) {
|
|
219
|
+
if (!tags || !tags.length) {
|
|
220
|
+
log.dim(' (外部仓库暂无分类目录)');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
log.raw('');
|
|
224
|
+
log.info('外部知识库分类目录:');
|
|
225
|
+
log.raw(` ${'分类'.padEnd(20)} ${'文档数'.padEnd(6)} 文档列表`);
|
|
226
|
+
log.raw(` ${'─'.repeat(70)}`);
|
|
227
|
+
for (const t of tags) {
|
|
228
|
+
const name = (t.tag_name || path.basename(t.directory)).slice(0, 20);
|
|
229
|
+
const count = String(t.document_count || 0).padEnd(6);
|
|
230
|
+
const docList = (t.documents || []).slice(0, 4).join(', ');
|
|
231
|
+
const more = (t.documents || []).length > 4 ? ` …+${t.documents.length - 4}` : '';
|
|
232
|
+
log.raw(` ${name.padEnd(20)} ${count} ${docList}${more}`);
|
|
233
|
+
}
|
|
234
|
+
log.raw('');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function printDepositTable(plan, { mrMode = false, hasExternal = false } = {}) {
|
|
238
|
+
const COL = { idx: 3, op: 2, cat: 22, target: 44, source: 18 };
|
|
239
|
+
const hr = '─'.repeat(COL.idx + COL.op + COL.cat + COL.target + COL.source + 8);
|
|
240
|
+
log.raw('');
|
|
241
|
+
log.raw(` ${'#'.padEnd(COL.idx)} ${'op'.padEnd(COL.op)} ${'分类'.padEnd(COL.cat)} ${'知识文件'.padEnd(COL.target)} 来源`);
|
|
242
|
+
log.raw(` ${hr}`);
|
|
243
|
+
let idx = 0;
|
|
244
|
+
for (const p of plan) {
|
|
245
|
+
if (p.action === 'noop') continue;
|
|
246
|
+
idx++;
|
|
247
|
+
const op = p.action === 'create' ? 'A' : 'M';
|
|
248
|
+
const cat = `${p.domain}/${p.category}`;
|
|
249
|
+
const catStr = cat.length > COL.cat ? '…' + cat.slice(-(COL.cat - 1)) : cat;
|
|
250
|
+
const tgt = p.filename.length > COL.target ? '…' + p.filename.slice(-(COL.target - 1)) : p.filename;
|
|
251
|
+
const src = p.source.length > COL.source ? p.source.slice(0, COL.source - 1) + '…' : p.source;
|
|
252
|
+
log.raw(` ${String(idx).padEnd(COL.idx)} ${op.padEnd(COL.op)} ${catStr.padEnd(COL.cat)} ${tgt.padEnd(COL.target)} ${src}`);
|
|
253
|
+
}
|
|
254
|
+
if (plan.some((p) => p.action === 'noop')) {
|
|
255
|
+
const nCount = plan.filter((p) => p.action === 'noop').length;
|
|
256
|
+
log.raw(` ${'·'.padEnd(COL.idx + COL.op + 1)} (${nCount} 项未变更,已跳过)`);
|
|
257
|
+
}
|
|
258
|
+
log.raw('');
|
|
259
|
+
|
|
260
|
+
const total = plan.filter((p) => p.action !== 'noop').length;
|
|
261
|
+
log.dim(` 共 ${total} 个知识文档将被写入(每个 change 通常生成 2-3 个)`);
|
|
262
|
+
log.raw('');
|
|
263
|
+
|
|
264
|
+
if (mrMode) {
|
|
265
|
+
if (hasExternal) {
|
|
266
|
+
log.info('MR 模式 (外部仓库):确认后写入外部知识库仓库 → 建分支 → 创建 MR。');
|
|
267
|
+
log.info('合并后 CI 自动同步到 WeKnora,新增文件 knowledge_id 由 CI 回写。');
|
|
268
|
+
} else {
|
|
269
|
+
log.info('MR 模式:确认后自动建分支 + 创建 PR/MR,reviewer 在 Git 平台 review 后合并。');
|
|
270
|
+
}
|
|
271
|
+
log.raw('');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Interactive confirmation prompt.
|
|
277
|
+
* Returns the confirmed subset of items, or null if skipped.
|
|
278
|
+
*/
|
|
279
|
+
async function interactiveConfirm(actionable, { mrMode = false } = {}) {
|
|
280
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
281
|
+
const ask = (q) => new Promise((res) => rl.question(q, res));
|
|
282
|
+
|
|
283
|
+
log.raw(' 确认沉淀方式:');
|
|
284
|
+
log.raw(` 回车 = 全部确认${mrMode ? ' → 建分支 + 创建 PR/MR' : ''}`);
|
|
285
|
+
log.raw(' 1,3,5 = 仅确认指定序号');
|
|
286
|
+
log.raw(' s / skip = 跳过不写入');
|
|
287
|
+
log.raw('');
|
|
288
|
+
|
|
289
|
+
let answer;
|
|
290
|
+
try {
|
|
291
|
+
answer = (await ask(' > ')).trim();
|
|
292
|
+
} finally {
|
|
293
|
+
rl.close();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!answer || answer.toLowerCase() === 'all') {
|
|
297
|
+
return actionable;
|
|
298
|
+
}
|
|
299
|
+
if (answer.toLowerCase() === 's' || answer.toLowerCase() === 'skip') {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Parse "1,3,5" or "1 3 5"
|
|
304
|
+
const indices = new Set(
|
|
305
|
+
answer.split(/[,\s]+/)
|
|
306
|
+
.map((s) => parseInt(s, 10) - 1)
|
|
307
|
+
.filter((i) => !isNaN(i) && i >= 0 && i < actionable.length),
|
|
308
|
+
);
|
|
309
|
+
if (!indices.size) {
|
|
310
|
+
log.warn('无效输入,跳过 deposit。');
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
return actionable.filter((_, i) => indices.has(i));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function runSync(root, flags) {
|
|
317
|
+
const dryRun = !!(flags.dryRun || flags['dry-run']);
|
|
318
|
+
const force = !!(flags.force || flags.f);
|
|
319
|
+
const all = !!flags.all;
|
|
320
|
+
const providerName = await resolveSyncProviderName(root, flags);
|
|
321
|
+
if (providerName === false) { process.exitCode = 1; return; }
|
|
322
|
+
let slug = null;
|
|
323
|
+
if (!all) {
|
|
324
|
+
slug = await helpers.resolveSlug(root, flags, []);
|
|
325
|
+
if (!slug) { process.exitCode = 1; return; }
|
|
326
|
+
}
|
|
327
|
+
if (force) log.warn('--force: clearing stored UUIDs, all files will be re-uploaded');
|
|
328
|
+
let result;
|
|
329
|
+
try {
|
|
330
|
+
result = await sync.sync(root, { dryRun, force, providerName, slug });
|
|
331
|
+
} catch (e) {
|
|
332
|
+
log.error(e.message);
|
|
333
|
+
process.exitCode = 1; return;
|
|
334
|
+
}
|
|
335
|
+
for (const a of result.actions) {
|
|
336
|
+
log.raw(` ${a.kind.padEnd(7)} ${a.dir}/${a.name}${a.uuid ? ' (uuid=' + a.uuid + ')' : ''}${a.error ? ' !! ' + a.error : ''}`);
|
|
337
|
+
}
|
|
338
|
+
const s = result.summary;
|
|
339
|
+
const tagPart = s.tag ? ` tag=${s.tag}` : '';
|
|
340
|
+
if (!s.scanned && result.actions.length === 0) {
|
|
341
|
+
if (slug) {
|
|
342
|
+
log.warn(`no knowledge markdown found for current change "${slug}"; nothing was synced.`);
|
|
343
|
+
log.dim(` checked: ${paths.workspaceKnowledgeDir(slug)}`);
|
|
344
|
+
log.dim(` ${path.join(paths.devflowDir(root), 'changes', slug, 'knowledge')} (legacy)`);
|
|
345
|
+
} else {
|
|
346
|
+
log.warn('no knowledge markdown found under workspace knowledge roots; nothing was synced.');
|
|
347
|
+
log.dim(` checked: ${paths.workspaceKnowledgeRoot()}`);
|
|
348
|
+
log.dim(` ${path.join(paths.changesDir(root), '<slug>', 'knowledge')}`);
|
|
349
|
+
log.dim(` ${paths.knowledgeDir(root)} (legacy)`);
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const scope = slug ? ` change=${slug}` : ' all';
|
|
354
|
+
log.ok(`${dryRun ? '[dry-run] ' : ''}scope=${scope} create=${s.create} update=${s.update} delete=${s.delete} noop=${s.noop}${tagPart}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function resolveSyncProviderName(root, flags) {
|
|
358
|
+
if (flags.provider) return flags.provider;
|
|
359
|
+
const configured = await loader.listConfigured(root);
|
|
360
|
+
const kbProviders = configured.filter((p) => p.type === 'kb');
|
|
361
|
+
if (kbProviders.length === 0) return undefined;
|
|
362
|
+
if (kbProviders.length === 1) return kbProviders[0].name;
|
|
363
|
+
|
|
364
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
365
|
+
log.error('multiple kb providers configured; specify one with --provider=<name>.');
|
|
366
|
+
for (const p of kbProviders) log.dim(` - ${p.name} (${p.driver}, ${p.source})`);
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
log.info('检测到多个知识库配置,请选择本次同步的业务线:');
|
|
371
|
+
kbProviders.forEach((p, i) => {
|
|
372
|
+
log.raw(` ${i + 1}. ${p.name} (${p.driver}, ${p.source})`);
|
|
373
|
+
});
|
|
374
|
+
const answer = await ask('选择编号> ');
|
|
375
|
+
const idx = Number.parseInt(String(answer).trim(), 10);
|
|
376
|
+
if (!Number.isInteger(idx) || idx < 1 || idx > kbProviders.length) {
|
|
377
|
+
log.error('invalid provider selection.');
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
return kbProviders[idx - 1].name;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function ask(prompt) {
|
|
384
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
385
|
+
return new Promise((resolve) => rl.question(prompt, (answer) => {
|
|
386
|
+
rl.close();
|
|
387
|
+
resolve(answer);
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* devflow knowledge reparse
|
|
393
|
+
* Trigger re-parse/re-embedding for all knowledge items stored in .meta.json.
|
|
394
|
+
* Useful when WeKnora shows "暂存内容未参与检索" after configuring an embedding model.
|
|
395
|
+
*/
|
|
396
|
+
async function runReparse(root, flags) {
|
|
397
|
+
const { knowledgeRoots, readMeta } = require('../../knowledge/registry.js');
|
|
398
|
+
const roots = knowledgeRoots(root);
|
|
399
|
+
if (!roots.length) {
|
|
400
|
+
log.error('no workspace knowledge/ dir found');
|
|
401
|
+
process.exitCode = 1; return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
let provider;
|
|
405
|
+
try {
|
|
406
|
+
provider = await loader.load(root, flags.provider ? { name: flags.provider } : { type: 'kb' });
|
|
407
|
+
} catch (e) {
|
|
408
|
+
log.error(`failed to load kb provider: ${e.message}`);
|
|
409
|
+
process.exitCode = 1; return;
|
|
410
|
+
}
|
|
411
|
+
if (typeof provider.reparse !== 'function') {
|
|
412
|
+
log.error('current kb provider does not support reparse (only kb-weknora does)');
|
|
413
|
+
process.exitCode = 1; return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Collect all UUIDs from .meta.json files
|
|
417
|
+
const uuids = [];
|
|
418
|
+
const walk = async (dir) => {
|
|
419
|
+
const entries = await require('fs/promises').readdir(dir, { withFileTypes: true });
|
|
420
|
+
for (const e of entries) {
|
|
421
|
+
const full = require('path').join(dir, e.name);
|
|
422
|
+
if (e.isDirectory()) { await walk(full); continue; }
|
|
423
|
+
if (e.name !== '.meta.json') continue;
|
|
424
|
+
const meta = await readMeta(dir).catch(() => null);
|
|
425
|
+
if (!meta || !meta.files) continue;
|
|
426
|
+
for (const [name, info] of Object.entries(meta.files)) {
|
|
427
|
+
if (info.uuid) uuids.push({ uuid: info.uuid, name });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
for (const kbDir of roots) await walk(kbDir);
|
|
432
|
+
|
|
433
|
+
if (!uuids.length) {
|
|
434
|
+
log.warn('no synced items found in .meta.json — run "devflow knowledge sync" first');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
log.info(`triggering reparse for ${uuids.length} item(s)...`);
|
|
439
|
+
let ok = 0, fail = 0;
|
|
440
|
+
for (const { uuid, name } of uuids) {
|
|
441
|
+
try {
|
|
442
|
+
await provider.reparse(uuid);
|
|
443
|
+
log.raw(` ✓ ${name} (${uuid})`);
|
|
444
|
+
ok++;
|
|
445
|
+
} catch (e) {
|
|
446
|
+
log.raw(` ✗ ${name} (${uuid}) ${e.message}`);
|
|
447
|
+
fail++;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
log.ok(`reparse triggered: ${ok} ok, ${fail} failed`);
|
|
451
|
+
if (ok) log.dim('WeKnora will embed asynchronously — check status in a few minutes.');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function validate(root, _flags) {
|
|
455
|
+
const { knowledgeRoots } = require('../../knowledge/registry.js');
|
|
456
|
+
const roots = knowledgeRoots(root);
|
|
457
|
+
if (!roots.length) { log.error('no workspace knowledge/ dir'); process.exitCode = 1; return; }
|
|
458
|
+
let issues = 0;
|
|
459
|
+
for (const dir of roots) {
|
|
460
|
+
for (const cat of await listCategories(dir)) {
|
|
461
|
+
const meta = path.join(cat, '.meta.json');
|
|
462
|
+
if (!fsSync.existsSync(meta)) {
|
|
463
|
+
log.warn(`missing .meta.json in ${cat}`);
|
|
464
|
+
issues++; continue;
|
|
465
|
+
}
|
|
466
|
+
try { JSON.parse(await fs.readFile(meta, 'utf8')); }
|
|
467
|
+
catch { log.error(`invalid .meta.json: ${meta}`); issues++; }
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (issues === 0) log.ok('knowledge: meta consistency ok');
|
|
471
|
+
else process.exitCode = 1;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function runMigrate(root, flags) {
|
|
475
|
+
const from = flags.from || 'arb-7';
|
|
476
|
+
const to = flags.to || 'improved-9';
|
|
477
|
+
const dryRun = !!(flags.dryRun || flags['dry-run']);
|
|
478
|
+
let report;
|
|
479
|
+
try { report = await migrate.migrate(root, { from, to, dryRun }); }
|
|
480
|
+
catch (e) { log.error(e.message); process.exitCode = 1; return; }
|
|
481
|
+
log.info(`migrate ${from} -> ${to} (dryRun=${dryRun})`);
|
|
482
|
+
log.raw(` moved: ${report.moved}`);
|
|
483
|
+
log.raw(` skipped: ${report.skipped}`);
|
|
484
|
+
log.raw(` conflicts: ${report.conflicts}`);
|
|
485
|
+
if (report.details.conflicts.length) {
|
|
486
|
+
log.warn('conflicts (no overwrite):');
|
|
487
|
+
for (const c of report.details.conflicts) log.raw(` ${c.src} -> ${c.dst}`);
|
|
488
|
+
}
|
|
489
|
+
if (!dryRun) log.ok('report: devflow/knowledge/.migration.log');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function listCategories(dir) {
|
|
493
|
+
const out = [];
|
|
494
|
+
const domains = await fs.readdir(dir, { withFileTypes: true });
|
|
495
|
+
for (const d of domains) {
|
|
496
|
+
if (!d.isDirectory()) continue;
|
|
497
|
+
const ds = path.join(dir, d.name);
|
|
498
|
+
const cats = await fs.readdir(ds, { withFileTypes: true });
|
|
499
|
+
for (const c of cats) if (c.isDirectory()) out.push(path.join(ds, c.name));
|
|
500
|
+
}
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function help() {
|
|
505
|
+
log.raw(`devflow knowledge <init|query|deposit|sync|validate|migrate> [opts]
|
|
506
|
+
|
|
507
|
+
init ensure skeleton (created by devflow init)
|
|
508
|
+
query "<kw1> <kw2>" local + remote search; writes changes/<slug>/knowledge.md
|
|
509
|
+
flags: --k=8 --local-only --slug=<slug>
|
|
510
|
+
deposit publish workspace knowledge/design/incident reports → knowledge/
|
|
511
|
+
shows plan + interactive confirm (--yes to skip, --dry-run to preview)
|
|
512
|
+
flags: --slug=<slug> --dry-run --yes --mr
|
|
513
|
+
deposit --mr after writing, auto-create branch + PR/MR for reviewer review
|
|
514
|
+
(needs gh / glab CLI, or a configured vcs provider)
|
|
515
|
+
sync push current change knowledge/ to kb provider (sha256 incremental)
|
|
516
|
+
flags: --slug=<slug> --provider=<name> --dry-run --all
|
|
517
|
+
validate check .meta.json consistency
|
|
518
|
+
migrate reshape arb-7 → improved-9 layout
|
|
519
|
+
flags: --from=arb-7 --to=improved-9 --dry-run
|
|
520
|
+
`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
module.exports = { run };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const log = require('../../utils/log.js');
|
|
3
|
+
const providers = require('../../providers/loader.js');
|
|
4
|
+
|
|
5
|
+
async function run({ sub, flags = {}, positional = [], cwd } = {}) {
|
|
6
|
+
const root = cwd || process.cwd();
|
|
7
|
+
const action = sub || positional[0] || 'query';
|
|
8
|
+
if (action !== 'query') {
|
|
9
|
+
log.error('usage: devflow logs query [--provider=<name>] [--query=...] [--trace-id=...] [--request-id=...] [--since=15m] [--limit=50] [--dry-run]');
|
|
10
|
+
process.exitCode = 2;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const provider = await providers.load(root, flags.provider ? { name: flags.provider } : { type: 'observability' });
|
|
14
|
+
if (typeof provider.query !== 'function') {
|
|
15
|
+
log.error(`provider ${provider.name || provider.driver} does not support log query`);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const result = await provider.query({
|
|
20
|
+
query: flags.query || positional[1] || '',
|
|
21
|
+
traceId: flags.traceId || flags['trace-id'] || '',
|
|
22
|
+
requestId: flags.requestId || flags['request-id'] || '',
|
|
23
|
+
since: flags.since || '15m',
|
|
24
|
+
limit: Number(flags.limit || 50),
|
|
25
|
+
dryRun: flags.dryRun === true || flags['dry-run'] === true,
|
|
26
|
+
});
|
|
27
|
+
printResult(result);
|
|
28
|
+
if (result.status === 'fail') process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function printResult(result) {
|
|
32
|
+
log.ok(`logs query: ${result.status}`);
|
|
33
|
+
if (result.command) log.dim(`command: ${result.command}`);
|
|
34
|
+
if (result.request) {
|
|
35
|
+
log.raw(JSON.stringify(result.request, null, 2));
|
|
36
|
+
}
|
|
37
|
+
if (result.output) log.raw(result.output);
|
|
38
|
+
if (Array.isArray(result.items) && result.items.length) {
|
|
39
|
+
for (const item of result.items) log.raw(typeof item === 'string' ? item : JSON.stringify(item));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { run, _internals: { printResult } };
|