@ai-content-space/loopx 0.1.9 → 0.1.10
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/README.md +5 -1
- package/README.zh-CN.md +5 -1
- package/package.json +1 -1
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/skills/archive/SKILL.md +1 -1
- package/plugins/loopx/skills/autopilot/SKILL.md +1 -1
- package/plugins/loopx/skills/build/SKILL.md +1 -1
- package/plugins/loopx/skills/clarify/SKILL.md +1 -1
- package/plugins/loopx/skills/debug/SKILL.md +1 -1
- package/plugins/loopx/skills/go-style/SKILL.md +1 -1
- package/plugins/loopx/skills/kratos/SKILL.md +1 -1
- package/plugins/loopx/skills/plan/SKILL.md +12 -1
- package/plugins/loopx/skills/review/SKILL.md +1 -1
- package/plugins/loopx/skills/tdd/SKILL.md +1 -1
- package/plugins/loopx/skills/verify/SKILL.md +1 -1
- package/skills/archive/SKILL.md +1 -1
- package/skills/autopilot/SKILL.md +1 -1
- package/skills/build/SKILL.md +1 -1
- package/skills/clarify/SKILL.md +1 -1
- package/skills/debug/SKILL.md +1 -1
- package/skills/go-style/SKILL.md +1 -1
- package/skills/kratos/SKILL.md +1 -1
- package/skills/plan/SKILL.md +12 -1
- package/skills/review/SKILL.md +1 -1
- package/skills/tdd/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/src/cli.mjs +2 -0
- package/src/html-views.mjs +463 -35
- package/src/plan-runtime.mjs +2 -1
- package/src/runtime-maintenance.mjs +55 -14
- package/src/workflow.mjs +140 -13
|
@@ -39,6 +39,8 @@ const CHANGE_ARTIFACT_FILE_MAP = {
|
|
|
39
39
|
graph: 'artifact-graph.json',
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
const PLAN_ARTIFACTS = ['plan.md', 'architecture.md', 'development-plan.md', 'test-plan.md'];
|
|
43
|
+
|
|
42
44
|
function normalizeSlug(raw) {
|
|
43
45
|
return String(raw || '')
|
|
44
46
|
.trim()
|
|
@@ -47,6 +49,44 @@ function normalizeSlug(raw) {
|
|
|
47
49
|
.replace(/^-+|-+$/g, '');
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
function containsChineseText(text) {
|
|
53
|
+
const chineseChars = text.match(/[\u3400-\u9fff]/g) || [];
|
|
54
|
+
const latinChars = text.match(/[A-Za-z]/g) || [];
|
|
55
|
+
const signalChars = chineseChars.length + latinChars.length;
|
|
56
|
+
if (signalChars === 0) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return chineseChars.length >= 40 || (chineseChars.length >= 8 && chineseChars.length / signalChars >= 0.2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function legacyPlanArtifactBlockers(workflowRoot) {
|
|
63
|
+
const blockers = [];
|
|
64
|
+
for (const name of PLAN_ARTIFACTS) {
|
|
65
|
+
const path = join(workflowRoot, name);
|
|
66
|
+
const key = name
|
|
67
|
+
.replace(/\.md$/, '')
|
|
68
|
+
.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
69
|
+
if (!existsSync(path)) {
|
|
70
|
+
blockers.push(`missing_plan_artifact_${key}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const text = await readFile(path, 'utf8');
|
|
74
|
+
if (!containsChineseText(text)) {
|
|
75
|
+
blockers.push(`plan_artifact_not_chinese_${key}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!existsSync(join(workflowRoot, 'requirement-traceability.md'))) {
|
|
79
|
+
blockers.push('missing_requirement_traceability');
|
|
80
|
+
}
|
|
81
|
+
if (!existsSync(join(workflowRoot, 'plan-delegation-decision.md'))) {
|
|
82
|
+
blockers.push('missing_plan_delegation_decision');
|
|
83
|
+
}
|
|
84
|
+
if (!existsSync(join(workflowRoot, 'plan-reviews'))) {
|
|
85
|
+
blockers.push('missing_plan_review_artifacts');
|
|
86
|
+
}
|
|
87
|
+
return blockers;
|
|
88
|
+
}
|
|
89
|
+
|
|
50
90
|
export function resolveLoopxRoot(cwd) {
|
|
51
91
|
return join(resolve(cwd), '.loopx');
|
|
52
92
|
}
|
|
@@ -332,24 +372,25 @@ async function migrateLegacyWorkflowState(cwd, slug, workflowRoot, legacyState)
|
|
|
332
372
|
const canonicalPlanPath = join(resolveLoopxRoot(cwd), 'plans', `prd-${slug}.md`);
|
|
333
373
|
const canonicalTestSpecPath = join(resolveLoopxRoot(cwd), 'plans', `test-spec-${slug}.md`);
|
|
334
374
|
const baseState = createMigratedWorkflowBaseState(slug, legacyState, change);
|
|
335
|
-
const
|
|
336
|
-
|
|
375
|
+
const planBlockers = await legacyPlanArtifactBlockers(workflowRoot);
|
|
376
|
+
const planDocsComplete = planBlockers.length === 0;
|
|
337
377
|
const executionRecordStatus = await inferExecutionStatus(workflowRoot);
|
|
338
|
-
const planState =
|
|
378
|
+
const planState = PLAN_ARTIFACTS.some((name) => existsSync(join(workflowRoot, name))) ? {
|
|
339
379
|
current_stage: STAGES.PLAN,
|
|
340
|
-
stage_status: 'awaiting-approval',
|
|
341
|
-
plan_package_status: 'complete',
|
|
380
|
+
stage_status: planDocsComplete ? 'awaiting-approval' : 'blocked',
|
|
381
|
+
plan_package_status: planDocsComplete ? 'complete' : 'partial',
|
|
342
382
|
plan_current_iteration: 1,
|
|
343
|
-
plan_principles_resolved:
|
|
344
|
-
plan_options_reviewed:
|
|
345
|
-
plan_architect_review_status: 'complete',
|
|
346
|
-
plan_critic_verdict: 'approve',
|
|
347
|
-
plan_acceptance_criteria_testable:
|
|
348
|
-
plan_verification_steps_resolved:
|
|
349
|
-
plan_execution_inputs_resolved:
|
|
350
|
-
plan_docs_status: 'complete',
|
|
383
|
+
plan_principles_resolved: planDocsComplete,
|
|
384
|
+
plan_options_reviewed: planDocsComplete,
|
|
385
|
+
plan_architect_review_status: planDocsComplete ? 'complete' : 'not-started',
|
|
386
|
+
plan_critic_verdict: planDocsComplete ? 'approve' : 'none',
|
|
387
|
+
plan_acceptance_criteria_testable: planDocsComplete,
|
|
388
|
+
plan_verification_steps_resolved: planDocsComplete,
|
|
389
|
+
plan_execution_inputs_resolved: planDocsComplete,
|
|
390
|
+
plan_docs_status: planDocsComplete ? 'complete' : 'partial',
|
|
391
|
+
plan_blockers: planBlockers,
|
|
351
392
|
approval: {
|
|
352
|
-
plan: APPROVAL_STATES.APPROVED,
|
|
393
|
+
plan: planDocsComplete ? APPROVAL_STATES.APPROVED : APPROVAL_STATES.NOT_REQUESTED,
|
|
353
394
|
build: APPROVAL_STATES.NOT_REQUESTED,
|
|
354
395
|
review: APPROVAL_STATES.NOT_REQUESTED,
|
|
355
396
|
rollback: APPROVAL_STATES.NOT_REQUESTED,
|
package/src/workflow.mjs
CHANGED
|
@@ -416,6 +416,11 @@ function createInitialState(slug, profile) {
|
|
|
416
416
|
plan_execution_inputs_resolved: false,
|
|
417
417
|
plan_docs_status: 'missing',
|
|
418
418
|
plan_docs_artifact_paths: null,
|
|
419
|
+
plan_delegation_decision_path: null,
|
|
420
|
+
plan_delegation_mode: 'local',
|
|
421
|
+
plan_delegation_score: 0,
|
|
422
|
+
plan_delegation_triggers: [],
|
|
423
|
+
plan_delegation_reason: null,
|
|
419
424
|
plan_review_artifact_paths: [],
|
|
420
425
|
plan_review_history: [],
|
|
421
426
|
plan_blockers: [],
|
|
@@ -749,6 +754,93 @@ async function writeRequirementTraceabilityArtifact({ root, sourceSpecPath, sour
|
|
|
749
754
|
};
|
|
750
755
|
}
|
|
751
756
|
|
|
757
|
+
function delegationDecisionForPlan(sourceText, plannerDraft) {
|
|
758
|
+
const source = String(sourceText || '');
|
|
759
|
+
const draft = [
|
|
760
|
+
plannerDraft.planText,
|
|
761
|
+
plannerDraft.architectureText,
|
|
762
|
+
plannerDraft.developmentPlanText,
|
|
763
|
+
plannerDraft.testPlanText,
|
|
764
|
+
].join('\n');
|
|
765
|
+
const combined = `${source}\n${draft}`;
|
|
766
|
+
const requirementCount = sourceRequirementItems(source).length;
|
|
767
|
+
const lineCount = source.split('\n').filter((line) => line.trim()).length;
|
|
768
|
+
const triggers = [];
|
|
769
|
+
let score = 0;
|
|
770
|
+
|
|
771
|
+
const addTrigger = (trigger, weight) => {
|
|
772
|
+
if (!triggers.includes(trigger)) {
|
|
773
|
+
triggers.push(trigger);
|
|
774
|
+
score += weight;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
if (requirementCount >= 12 || lineCount >= 180) {
|
|
779
|
+
addTrigger('large_requirement_surface', 3);
|
|
780
|
+
} else if (requirementCount >= 6 || lineCount >= 90) {
|
|
781
|
+
addTrigger('medium_requirement_surface', 2);
|
|
782
|
+
}
|
|
783
|
+
if (/资金|资产|清算|结算|交易|订单|风控|权限|安全|合规|审计|corporate action|settlement|trading|order|asset|security|auth|permission|compliance|audit|financial/i.test(combined)) {
|
|
784
|
+
addTrigger('high_risk_domain', 3);
|
|
785
|
+
}
|
|
786
|
+
if (/api|接口|service|biz|data|database|schema|migration|数据库|迁移|worker|cron|frontend|后台|部署|deploy/i.test(combined)) {
|
|
787
|
+
addTrigger('cross_module_scope', 2);
|
|
788
|
+
}
|
|
789
|
+
if (/状态机|幂等|补偿|差异|回滚|并发|重试|eventual|idempot|retry|rollback|concurrency|state machine/i.test(combined)) {
|
|
790
|
+
addTrigger('state_or_integrity_complexity', 2);
|
|
791
|
+
}
|
|
792
|
+
if (/e2e|集成测试|integration|regression|回归|验收|acceptance|fixture|mock|真实数据|external/i.test(combined)) {
|
|
793
|
+
addTrigger('verification_complexity', 1);
|
|
794
|
+
}
|
|
795
|
+
if (/多个方案|备选|取舍|tradeoff|alternative|ADR|architecture/i.test(combined)) {
|
|
796
|
+
addTrigger('architectural_tradeoff', 1);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const mode = score >= 7 ? 'parallel-review' : (score >= 4 ? 'critic-only' : 'local');
|
|
800
|
+
const reason = mode === 'parallel-review'
|
|
801
|
+
? '高风险或跨模块规划,建议独立 Planner/Architect/Critic 视角并行审查。'
|
|
802
|
+
: mode === 'critic-only'
|
|
803
|
+
? '存在中等复杂度或验证风险,建议至少引入独立 critic 复核 PRD 覆盖和风险。'
|
|
804
|
+
: '范围较小或风险较低,本地顺序 Planner/Architect/Critic 审阅足够。';
|
|
805
|
+
|
|
806
|
+
return {
|
|
807
|
+
mode,
|
|
808
|
+
score,
|
|
809
|
+
triggers,
|
|
810
|
+
reason,
|
|
811
|
+
current_runtime_execution: 'local-sequential',
|
|
812
|
+
execution_note: '当前 runtime 记录委派决策依据;是否实际启动 native subagents 仍受执行环境和用户授权约束。',
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async function writePlanDelegationDecisionArtifact({ root, sourceText, plannerDraft }) {
|
|
817
|
+
const decision = delegationDecisionForPlan(sourceText, plannerDraft);
|
|
818
|
+
const path = artifactPath(root, 'plan-delegation-decision.md');
|
|
819
|
+
await writeText(path, [
|
|
820
|
+
'# Plan Delegation Decision',
|
|
821
|
+
'',
|
|
822
|
+
`- mode: ${decision.mode}`,
|
|
823
|
+
`- score: ${decision.score}`,
|
|
824
|
+
`- current_runtime_execution: ${decision.current_runtime_execution}`,
|
|
825
|
+
`- reason: ${decision.reason}`,
|
|
826
|
+
'',
|
|
827
|
+
'## Triggers',
|
|
828
|
+
'',
|
|
829
|
+
...(decision.triggers.length > 0 ? decision.triggers.map((item) => `- ${item}`) : ['- none']),
|
|
830
|
+
'',
|
|
831
|
+
'## Guidance',
|
|
832
|
+
'',
|
|
833
|
+
'- local: 低风险、小范围、单模块任务,本地顺序 Planner/Architect/Critic 即可。',
|
|
834
|
+
'- critic-only: 中等风险或覆盖面较宽,至少需要独立 critic 复核 PRD 覆盖、验证和遗漏风险。',
|
|
835
|
+
'- parallel-review: 高风险、多模块、状态/资产/安全相关任务,建议独立 Planner/Architect/Critic 视角并行审查。',
|
|
836
|
+
'',
|
|
837
|
+
'## Runtime Note',
|
|
838
|
+
'',
|
|
839
|
+
`- ${decision.execution_note}`,
|
|
840
|
+
].join('\n'));
|
|
841
|
+
return { path, ...decision };
|
|
842
|
+
}
|
|
843
|
+
|
|
752
844
|
function frontmatterList(text, key) {
|
|
753
845
|
if (!text.startsWith('---\n')) {
|
|
754
846
|
return [];
|
|
@@ -1522,6 +1614,31 @@ function containsChineseText(text) {
|
|
|
1522
1614
|
return chineseChars.length >= 40 || (chineseChars.length >= 8 && chineseChars.length / signalChars >= 0.2);
|
|
1523
1615
|
}
|
|
1524
1616
|
|
|
1617
|
+
async function planLanguageBlockers(pathsByKey) {
|
|
1618
|
+
const blockers = [];
|
|
1619
|
+
for (const [key, path] of Object.entries(pathsByKey)) {
|
|
1620
|
+
if (!existsSync(path)) {
|
|
1621
|
+
blockers.push(`missing_plan_artifact_${key}`);
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
const text = await readFile(path, 'utf8');
|
|
1625
|
+
if (!containsChineseText(text)) {
|
|
1626
|
+
blockers.push(`plan_artifact_not_chinese_${key}`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return blockers;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function planReviewArtifactBlockers(state) {
|
|
1633
|
+
if (!Array.isArray(state.plan_review_artifact_paths) || state.plan_review_artifact_paths.length === 0) {
|
|
1634
|
+
return ['missing_plan_review_artifacts'];
|
|
1635
|
+
}
|
|
1636
|
+
const latest = state.plan_review_artifact_paths[state.plan_review_artifact_paths.length - 1] || {};
|
|
1637
|
+
return ['planner', 'architect', 'critic']
|
|
1638
|
+
.filter((key) => !latest[key] || !existsSync(latest[key]))
|
|
1639
|
+
.map((key) => `missing_plan_review_artifact_${key}`);
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1525
1642
|
async function ensurePlanWorkflowFromDirectSpec(cwd, directSpecPath, explicitSlug, options = {}) {
|
|
1526
1643
|
const resolvedSpecPath = resolve(cwd, directSpecPath);
|
|
1527
1644
|
const specText = await readFile(resolvedSpecPath, 'utf8');
|
|
@@ -1681,6 +1798,10 @@ async function readPlanCompletion(cwd, root, slug, state) {
|
|
|
1681
1798
|
if (!state.requirement_traceability_path || !existsSync(state.requirement_traceability_path)) {
|
|
1682
1799
|
blockers.push('missing_requirement_traceability');
|
|
1683
1800
|
}
|
|
1801
|
+
if (!state.plan_delegation_decision_path || !existsSync(state.plan_delegation_decision_path)) {
|
|
1802
|
+
blockers.push('missing_plan_delegation_decision');
|
|
1803
|
+
}
|
|
1804
|
+
blockers.push(...planReviewArtifactBlockers(state));
|
|
1684
1805
|
if (state.source_requirements_status && state.source_requirements_status !== 'complete') {
|
|
1685
1806
|
if (state.requirement_traceability_path && existsSync(state.requirement_traceability_path)) {
|
|
1686
1807
|
const traceabilityText = await readFile(state.requirement_traceability_path, 'utf8');
|
|
@@ -1695,22 +1816,16 @@ async function readPlanCompletion(cwd, root, slug, state) {
|
|
|
1695
1816
|
blockers.push(`source_requirements_${state.source_requirements_status}`);
|
|
1696
1817
|
}
|
|
1697
1818
|
}
|
|
1698
|
-
|
|
1819
|
+
blockers.push(...await planLanguageBlockers({
|
|
1699
1820
|
plan: artifactPath(root, 'plan.md'),
|
|
1700
1821
|
architecture: artifactPath(root, 'architecture.md'),
|
|
1701
1822
|
developmentPlan: artifactPath(root, 'development-plan.md'),
|
|
1702
1823
|
testPlan: artifactPath(root, 'test-plan.md'),
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1709
|
-
const text = await readFile(path, 'utf8');
|
|
1710
|
-
if (!containsChineseText(text)) {
|
|
1711
|
-
blockers.push(`plan_artifact_not_chinese_${key}`);
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1824
|
+
prd: state.plan_artifact_path || join(resolvePlansRoot(cwd), `prd-${slug}.md`),
|
|
1825
|
+
testSpec: state.test_spec_artifact_path || join(resolvePlansRoot(cwd), `test-spec-${slug}.md`),
|
|
1826
|
+
traceability: state.requirement_traceability_path || artifactPath(root, 'requirement-traceability.md'),
|
|
1827
|
+
delegationDecision: state.plan_delegation_decision_path || artifactPath(root, 'plan-delegation-decision.md'),
|
|
1828
|
+
}));
|
|
1714
1829
|
const changeStatus = await readChangeArtifactStatus(state.change_artifact_paths);
|
|
1715
1830
|
blockers.push(...changeStatus.blockers);
|
|
1716
1831
|
|
|
@@ -2835,7 +2950,9 @@ export async function clarifyStage(cwd, slug, { profile = 'standard' } = {}) {
|
|
|
2835
2950
|
},
|
|
2836
2951
|
});
|
|
2837
2952
|
await writeState(root, state);
|
|
2838
|
-
|
|
2953
|
+
const rendered = await renderPlanReadingViews(cwd, root, state, normalized);
|
|
2954
|
+
await writeState(root, rendered);
|
|
2955
|
+
return { root, state: rendered };
|
|
2839
2956
|
}
|
|
2840
2957
|
|
|
2841
2958
|
export async function approveStage(cwd, slug, { from, to }) {
|
|
@@ -3201,6 +3318,11 @@ export async function planStage(cwd, slug, options = {}) {
|
|
|
3201
3318
|
plannerDraft,
|
|
3202
3319
|
changeArtifactPaths,
|
|
3203
3320
|
});
|
|
3321
|
+
const delegationDecision = await writePlanDelegationDecisionArtifact({
|
|
3322
|
+
root,
|
|
3323
|
+
sourceText,
|
|
3324
|
+
plannerDraft,
|
|
3325
|
+
});
|
|
3204
3326
|
|
|
3205
3327
|
architectReview = await adapter.architect({
|
|
3206
3328
|
cwd,
|
|
@@ -3249,6 +3371,11 @@ export async function planStage(cwd, slug, options = {}) {
|
|
|
3249
3371
|
requirement_traceability_path: traceability.path,
|
|
3250
3372
|
source_requirements_status: traceability.status,
|
|
3251
3373
|
source_requirements_item_count: traceability.itemCount,
|
|
3374
|
+
plan_delegation_decision_path: delegationDecision.path,
|
|
3375
|
+
plan_delegation_mode: delegationDecision.mode,
|
|
3376
|
+
plan_delegation_score: delegationDecision.score,
|
|
3377
|
+
plan_delegation_triggers: delegationDecision.triggers,
|
|
3378
|
+
plan_delegation_reason: delegationDecision.reason,
|
|
3252
3379
|
change_id: normalizeSlug(changeId),
|
|
3253
3380
|
change_artifacts_status: changeArtifactStatus.status,
|
|
3254
3381
|
change_artifact_paths: changeArtifactPaths,
|