@tea-agent/loop-agent 0.23.1 → 0.24.1
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 +61 -1
- package/README.md +1 -1
- package/bin/agent-worker.js +0 -0
- package/dist/commands/init.js +2 -2
- package/dist/executors/shell-executor.js +20 -7
- package/dist/shared/operator/capabilities.js +475 -2
- package/dist/task/dag-source-paths.js +50 -0
- package/dist/task/frontend-project-capability.js +316 -0
- package/dist/task/task-demand-routing.js +432 -0
- package/dist/worker/console/app-data.js +2 -0
- package/dist/worker/console/chat/artifact-card.js +23 -0
- package/dist/worker/console/chat/chat-event-store.js +495 -0
- package/dist/worker/console/chat/chat-ui-policy.js +25 -0
- package/dist/worker/console/chat/compaction-errors.js +64 -0
- package/dist/worker/console/chat/composer-draft-store.js +45 -0
- package/dist/worker/console/chat/context-panel.js +54 -0
- package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
- package/dist/worker/console/chat/explore-tools.js +299 -0
- package/dist/worker/console/chat/human-gate-card.js +37 -0
- package/dist/worker/console/chat/interview-adapter.js +149 -0
- package/dist/worker/console/chat/operation-card.js +23 -0
- package/dist/worker/console/chat/pi-console-config.js +158 -0
- package/dist/worker/console/chat/pi-runtime.js +581 -43
- package/dist/worker/console/chat/repo-browser.js +140 -0
- package/dist/worker/console/chat/repo-walk.js +116 -0
- package/dist/worker/console/chat/resource-loader.js +18 -17
- package/dist/worker/console/chat/routes.js +1359 -65
- package/dist/worker/console/chat/runtime-context.js +24 -0
- package/dist/worker/console/chat/runtime-selection.js +37 -0
- package/dist/worker/console/chat/session-store.js +210 -11
- package/dist/worker/console/chat/shortcuts.js +15 -0
- package/dist/worker/console/chat/tool-adapter.js +81 -194
- package/dist/worker/console/chat/tools.js +72 -48
- package/dist/worker/console/chat/usage.js +37 -0
- package/dist/worker/console/chat/workspace-landing.js +56 -0
- package/dist/worker/console/dag-confirmation.js +42 -8
- package/dist/worker/console/human-gate-token.js +130 -0
- package/dist/worker/console/interview/grill-me.js +28 -5
- package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
- package/dist/worker/console/operation-runner.js +6 -2
- package/dist/worker/console/operation-sse.js +26 -0
- package/dist/worker/console/operator-actions.js +420 -7
- package/dist/worker/console/server.js +14 -2
- package/dist/worker/console/static/assets/index-Cv68Ge5n.js +27 -0
- package/dist/worker/console/static/assets/index-Dp_2sKGk.css +1 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/observe/routes.js +8 -4
- package/dist/workflows/dag/backend-test-intake-context.js +10 -12
- package/dist/workflows/dag/backend-test-markdown-workflow.js +232 -47
- package/dist/workflows/dag/backend-test-result-contract.js +233 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +23 -17
- package/dist/workflows/dag/frontend-lint-baseline.js +4 -4
- package/dist/workflows/dag/frontend-project-capability.js +1 -316
- package/dist/workflows/dag/frontend-worktree-diff.js +4 -1
- package/dist/workflows/dag/init-hybrid.js +46 -43
- package/dist/workflows/dag/rerun-plan.js +6 -10
- package/dist/workflows/dag/task-demand-routing.js +1 -383
- package/docs/README.md +1 -1
- package/docs/architecture/README.md +5 -5
- package/docs/architecture/evolution.md +4 -4
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/templates/backend-test-dag.json +2 -2
- package/docs/templates/frontend-implementation-contract.schema.json +1 -1
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-implementation/SKILL.md +7 -0
- package/skills/frontend-implementation/references/node-contracts.md +1 -1
- package/skills/frontend-verification/SKILL.md +4 -3
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/dist/worker/console/static/assets/index-DVl7Jxt5.js +0 -25
- package/dist/worker/console/static/assets/index-lVcIr9Ju.css +0 -1
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
const OUT_OF_SCOPE_HEADING = /^(?:非目标|不包含|范围外|不在范围|out\s+of\s+scope|non[-\s]?goals?)(?:\s*(?:[((].*[))]|[::].*|[/—–-]\s*.*))?$/i;
|
|
2
|
+
const FRONTEND_TASK_TYPE = /^\s*(?:[-*]\s*)?task\s+type\s*:\s*frontend-feature\s*$/im;
|
|
3
|
+
const BACKEND_TASK_TYPE = /^\s*(?:[-*]\s*)?task\s+type\s*:\s*backend-feature\s*$/im;
|
|
4
|
+
const FRONTEND_NEGATION_PATTERNS = [
|
|
5
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?:前端(?:页面|组件|界面|功能|交互)?|页面|组件|界面|用户界面|浏览器交互)/,
|
|
6
|
+
/(?:前端(?:页面|组件|界面|功能|交互)?|页面|组件|界面|用户界面).{0,12}(?:不在(?:本次)?范围|无需|不需要|不修改|不实现|不交付|不要|禁止|不得)/,
|
|
7
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?<![A-Za-z0-9_])UI(?![A-Za-z0-9_])/i,
|
|
8
|
+
/(?<![A-Za-z0-9_])UI(?![A-Za-z0-9_]).{0,12}(?:不在(?:本次)?范围|无需|不需要|不修改|不实现|不交付|不要|禁止|不得)/i,
|
|
9
|
+
/\b(?:backend|server[-\s]?side)\s+only\b/i,
|
|
10
|
+
/\b(?:no|without)\s+(?:frontend|front[-\s]?end|ui|user\s+interface)\b/i,
|
|
11
|
+
/\b(?:do\s+not|don't|must\s+not|should\s+not)\s+(?:(?:add|implement|build|create|update|modify|fix|refactor|render|style|change|touch)\s+)?(?:(?:the|any|a|an)\s+)?(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b/i,
|
|
12
|
+
/\bwithout\s+(?:(?:adding|implementing|building|creating|updating|modifying|fixing|refactoring|rendering|styling|changing|touching)\s+)?(?:(?:the|any|a|an)\s+)?(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b/i,
|
|
13
|
+
/\b(?:frontend|front[-\s]?end|ui|user\s+interface|page|component|form|modal|dialog)\b[^.?!;\n]{0,20}\b(?:is|are)?\s*(?:out\s+of\s+scope|excluded|not\s+(?:required|needed)|unchanged)\b/i,
|
|
14
|
+
];
|
|
15
|
+
const FRONTEND_DELIVERY_PATTERNS = [
|
|
16
|
+
/(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:前端(?:页面|组件|界面|功能|交互)|页面|组件|界面|表单|弹窗|对话框|用户界面|浏览器(?:页面|交互|行为))/,
|
|
17
|
+
/(?:前端(?:页面|组件|界面|功能|交互)|页面|组件|界面|表单|弹窗|对话框|用户界面|浏览器交互)[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)/,
|
|
18
|
+
/\b(?:add|implement|build|create|update|modify|fix|refactor|render|style)\b[^.?!;\n]{0,48}\b(?:front[-\s]?end\s+(?:page|component|view|form|modal|interaction|ui)|ui|form|modal|dialog|dashboard|navigation|toast|button|dropdown|user\s+interface|browser\s+interaction)\b/i,
|
|
19
|
+
];
|
|
20
|
+
const PATH_SUPPORTED_FRONTEND_DELIVERY_PATTERNS = [
|
|
21
|
+
/(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:页(?!\p{Script=Han})|表格|列表|按钮|导航|菜单|标签页|下拉框|选择器|提示|通知|加载状态|空状态|错误状态|交互|样式|布局)/u,
|
|
22
|
+
/(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?<![A-Za-z0-9_])(?:DOM|UI|React|Vue|Svelte|Angular)(?![A-Za-z0-9_])/i,
|
|
23
|
+
/\b(?:add|implement|build|create|update|modify|fix|refactor|render|style)\b[^.?!;\n]{0,48}\b(?:page|component|screen|layout|dom|react|vue|svelte|angular)\b/i,
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* User-visible product behavior that is commonly described without naming the
|
|
27
|
+
* frontend technology or artifact. These signals are never sufficient alone:
|
|
28
|
+
* they must be backed by a frontend source path or strong project framework
|
|
29
|
+
* evidence.
|
|
30
|
+
*/
|
|
31
|
+
const CONTEXT_SUPPORTED_FRONTEND_DELIVERY_PATTERNS = [
|
|
32
|
+
/(?:用户|运营人员|管理员|访客|客户)[^。!?;,,\n]{0,24}(?:列表|筛选|搜索|表单|按钮|弹窗|提示|通知|状态|工作台|导航|菜单|导出|下载)/,
|
|
33
|
+
/(?:点击|输入|选择|提交|筛选|搜索|翻页|跳转|关闭|打开|导出|下载)[^。!?;,,\n]{0,24}(?:显示|展示|提示|状态|结果|页面|工作台|错误|成功|失败|加载|下载)/,
|
|
34
|
+
/(?:字段错误|下载状态|加载中|加载失败|空状态|错误状态|成功状态|结果数量|表单校验|用户可见)/,
|
|
35
|
+
/\b(?:user|operator|admin|visitor|customer)\b[^.?!;\n]{0,48}\b(?:list|filter|search|form|button|modal|toast|notification|state|dashboard|navigation|menu|export|download)\b/i,
|
|
36
|
+
/\b(?:click|type|select|submit|filter|search|paginate|navigate|close|open|export|download)\b[^.?!;\n]{0,48}\b(?:show|display|feedback|state|result|page|dashboard|error|success|failure|loading|download)\b/i,
|
|
37
|
+
/\b(?:field error|download state|loading state|empty state|error state|success state|result count|form validation|user-visible)\b/i,
|
|
38
|
+
];
|
|
39
|
+
const BACKEND_DELIVERY_PATTERNS = [
|
|
40
|
+
/(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)[^。!?;,,\n]{0,24}(?:后端(?:服务|接口|逻辑|API)?|服务端(?:服务|接口|逻辑)?|接口|数据库|数据表(?!格)|数据库索引|索引|控制器|服务层|仓储层)/i,
|
|
41
|
+
/(?:后端(?:服务|接口|逻辑|API)?|服务端(?:服务|接口|逻辑)?|接口|数据库|数据表(?!格)|数据库索引|控制器|服务层|仓储层)[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)/i,
|
|
42
|
+
/(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)[^。!?;,,\n]{0,24}(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
|
|
43
|
+
/(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])[^。!?;,,\n]{0,16}(?:新增|实现|开发|修改|重构|修复|提供|创建|调整|优化|迁移|扩展)/i,
|
|
44
|
+
/\b(?:add|implement|build|create|update|modify|fix|refactor|migrate|extend)\b[^.?!;\n]{0,48}\b(?:backend|server[-\s]?side|api|endpoint|database|migration|controller|service|repository)\b/i,
|
|
45
|
+
];
|
|
46
|
+
const BACKEND_NEGATION_PATTERNS = [
|
|
47
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?:后端|服务端|接口|数据库|数据表(?!格)|索引)/,
|
|
48
|
+
/(?:不涉及|无需|不需要|不包含|不修改|不实现|不交付|不要|禁止|不得|排除).{0,16}(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
|
|
49
|
+
/\b(?:no|without)\s+(?:backend|server[-\s]?side|api|endpoint|database)\b/i,
|
|
50
|
+
/\b(?:do\s+not|don't|must\s+not|should\s+not)\s+(?:(?:add|implement|build|create|update|modify|fix|refactor|migrate|change|touch)\s+)?(?:(?:the|any|a|an)\s+)?(?:backend|server[-\s]?side|api|endpoint|database)\b/i,
|
|
51
|
+
];
|
|
52
|
+
const FRONTEND_PATH_PATTERNS = [
|
|
53
|
+
/(?:^|\/)apps\/web\/src(?:\/|$)/i,
|
|
54
|
+
/(?:^|\/)(?:frontend|client)(?:\/|$)/i,
|
|
55
|
+
/(?:^|\/)src\/(?:pages?|components?|views?)(?:\/|$)/i,
|
|
56
|
+
/\.(?:tsx|jsx|vue|svelte|css|scss|less)(?:$|[*?])/i,
|
|
57
|
+
];
|
|
58
|
+
const BACKEND_PATH_PATTERNS = [
|
|
59
|
+
/(?:^|\/)(?:backend|server|api|controllers?|services?|repositories?|db|database|migrations?)(?:\/|$)/i,
|
|
60
|
+
/\.(?:sql)(?:$|[*?])/i,
|
|
61
|
+
];
|
|
62
|
+
const NON_PRODUCT_PATH_SEGMENT = /(?:^|\/)(?:docs?|skills?|tests?|__tests__|e2e|cypress)(?:\/|$)/i;
|
|
63
|
+
const FRONTEND_PATH_EXCLUSIONS = NON_PRODUCT_PATH_SEGMENT;
|
|
64
|
+
const DOCUMENTATION_ONLY_MARKERS = /(?:文档|说明|指南|规范|测试用例|测试计划|评审|审查|\b(?:docs?|documentation|guide|specification|test\s+cases?|test\s+plan|review)\b)/i;
|
|
65
|
+
const DOCUMENTATION_ONLY_ACTIONS = /(?:新增|实现|开发|创建|编写|更新|修改|重构|修复|优化|渲染|搭建|改造|补充|生成|评审|审查|\b(?:add|implement|build|create|write|update|modify|refactor|fix|optimize|render|style|document|review|generate)\b)/i;
|
|
66
|
+
const PRODUCT_AND_DOCUMENT_DELIVERY = /(?:新增|实现|开发|修改|重构|修复|优化|创建|渲染|搭建|改造)[^。!?;,,\n]{0,24}(?:前端|页面|组件|界面|表单|交互|样式|布局|DOM|UI|React|Vue|Svelte|Angular|源码|代码)[^。!?;,,\n]{0,16}(?:并|同时|以及|和|与|\band\b)[^。!?;,,\n]{0,16}(?:文档|说明|指南|规范|\b(?:docs?|documentation|guide)\b)/i;
|
|
67
|
+
const TEST_ONLY_MARKERS = /(?:测试|用例|\b(?:e2e|unit|integration|component)\s+tests?\b|\btests?\b)/i;
|
|
68
|
+
const PRODUCT_AND_TEST_DELIVERY = /(?:并|同时|以及|和|与|\band\b).{0,12}(?:添加|新增|编写|补充|测试|\b(?:add|write|test)\b)/i;
|
|
69
|
+
const NON_PRODUCT_ALLOWED_PATH_PATTERNS = [
|
|
70
|
+
NON_PRODUCT_PATH_SEGMENT,
|
|
71
|
+
/\.(?:test|spec)\.[^/]+$/i,
|
|
72
|
+
/\.cy\.[^/]+$/i,
|
|
73
|
+
/\.mdx?(?:$|[*?])/i,
|
|
74
|
+
];
|
|
75
|
+
const SPECIAL_TEMPLATE_BY_TASK_KIND = {
|
|
76
|
+
"frontend-implementation": "frontend-implementation",
|
|
77
|
+
"frontend-test": "frontend-test-dag",
|
|
78
|
+
"backend-test": "backend-test-dag",
|
|
79
|
+
"knowledge-sync": "knowledge-sync-dag",
|
|
80
|
+
"knowledge-graph-bootstrap": "knowledge-graph-bootstrap-dag",
|
|
81
|
+
};
|
|
82
|
+
const TASK_KIND_ROUTING_REASON = {
|
|
83
|
+
"frontend-implementation": 'taskKind "frontend-implementation" selects the dedicated frontend DAG template',
|
|
84
|
+
"frontend-test": 'taskKind "frontend-test" selects the dedicated frontend test DAG template',
|
|
85
|
+
"backend-test": 'taskKind "backend-test" selects the dedicated backend test DAG template',
|
|
86
|
+
"knowledge-sync": 'taskKind "knowledge-sync" selects the dedicated knowledge-sync DAG template',
|
|
87
|
+
"knowledge-graph-bootstrap": 'taskKind "knowledge-graph-bootstrap" selects the dedicated knowledge-graph bootstrap DAG template',
|
|
88
|
+
};
|
|
89
|
+
const SPECIAL_REQUESTED_TEMPLATES = new Set([
|
|
90
|
+
"frontend-implementation",
|
|
91
|
+
"frontend-test-dag",
|
|
92
|
+
"backend-test-dag",
|
|
93
|
+
"knowledge-sync-dag",
|
|
94
|
+
"knowledge-graph-bootstrap-dag",
|
|
95
|
+
]);
|
|
96
|
+
function stripOutOfScopeSections(markdown) {
|
|
97
|
+
const output = [];
|
|
98
|
+
let skippedHeadingLevel = null;
|
|
99
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
100
|
+
const heading = /^(#{1,6})\s+(.+?)\s*$/.exec(line.trim());
|
|
101
|
+
if (heading) {
|
|
102
|
+
const level = heading[1].length;
|
|
103
|
+
if (skippedHeadingLevel !== null && level <= skippedHeadingLevel) {
|
|
104
|
+
skippedHeadingLevel = null;
|
|
105
|
+
}
|
|
106
|
+
if (OUT_OF_SCOPE_HEADING.test(heading[2].trim())) {
|
|
107
|
+
skippedHeadingLevel = level;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (skippedHeadingLevel === null)
|
|
112
|
+
output.push(line);
|
|
113
|
+
}
|
|
114
|
+
return output.join("\n");
|
|
115
|
+
}
|
|
116
|
+
function stripFencedCodeBlocks(markdown) {
|
|
117
|
+
const output = [];
|
|
118
|
+
let fenceCharacter = null;
|
|
119
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
120
|
+
const fence = /^\s*(`{3,}|~{3,})/.exec(line);
|
|
121
|
+
if (fence) {
|
|
122
|
+
const character = fence[1][0];
|
|
123
|
+
if (fenceCharacter === null)
|
|
124
|
+
fenceCharacter = character;
|
|
125
|
+
else if (fenceCharacter === character)
|
|
126
|
+
fenceCharacter = null;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (fenceCharacter === null)
|
|
130
|
+
output.push(line);
|
|
131
|
+
}
|
|
132
|
+
return output.join("\n");
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Normalize task prose before deterministic demand/capability signal checks.
|
|
136
|
+
* Examples and explicit non-goals must not become positive routing evidence.
|
|
137
|
+
*/
|
|
138
|
+
export function normalizeTaskRequirementText(markdown) {
|
|
139
|
+
return stripOutOfScopeSections(stripFencedCodeBlocks(markdown));
|
|
140
|
+
}
|
|
141
|
+
function splitClauses(text) {
|
|
142
|
+
return text
|
|
143
|
+
.split(/[。!?!?;;,,\r\n]+/)
|
|
144
|
+
.map((clause) => clause.trim())
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
}
|
|
147
|
+
function matchesAny(value, patterns) {
|
|
148
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
149
|
+
}
|
|
150
|
+
function isFrontendNegated(clause) {
|
|
151
|
+
return matchesAny(clause, FRONTEND_NEGATION_PATTERNS);
|
|
152
|
+
}
|
|
153
|
+
function isDocumentationOnlyClause(clause) {
|
|
154
|
+
if (!DOCUMENTATION_ONLY_ACTIONS.test(clause))
|
|
155
|
+
return false;
|
|
156
|
+
if (DOCUMENTATION_ONLY_MARKERS.test(clause)) {
|
|
157
|
+
return !PRODUCT_AND_DOCUMENT_DELIVERY.test(clause);
|
|
158
|
+
}
|
|
159
|
+
return TEST_ONLY_MARKERS.test(clause) && !PRODUCT_AND_TEST_DELIVERY.test(clause);
|
|
160
|
+
}
|
|
161
|
+
function masksExistingBackendDependency(clause) {
|
|
162
|
+
return clause
|
|
163
|
+
.replace(/(?:调用|使用|消费|对接|依赖)(?:现有|已有)?[^。!?;,,\n]{0,12}(?:后端|服务端|API|接口)/gi, " ")
|
|
164
|
+
.replace(/\b(?:call|consume|use|integrate\s+with|depend\s+on)\b[^.?!;\n]{0,24}\b(?:existing|current)?\s*(?:backend|server[-\s]?side|api|endpoint)\b/gi, " ");
|
|
165
|
+
}
|
|
166
|
+
function addSignal(signals, signal) {
|
|
167
|
+
if (signals.some((current) => current.id === signal.id))
|
|
168
|
+
return;
|
|
169
|
+
signals.push(signal);
|
|
170
|
+
}
|
|
171
|
+
function collectTextSignals(input) {
|
|
172
|
+
let frontendDelivery = false;
|
|
173
|
+
let pathSupportedFrontendDelivery = false;
|
|
174
|
+
let contextSupportedFrontendDelivery = false;
|
|
175
|
+
let backendDelivery = false;
|
|
176
|
+
let frontendNegated = false;
|
|
177
|
+
for (const clause of splitClauses(input.text)) {
|
|
178
|
+
const negated = isFrontendNegated(clause);
|
|
179
|
+
frontendNegated ||= negated;
|
|
180
|
+
const documentationOnly = isDocumentationOnlyClause(clause);
|
|
181
|
+
if (!negated &&
|
|
182
|
+
!documentationOnly &&
|
|
183
|
+
matchesAny(clause, FRONTEND_DELIVERY_PATTERNS)) {
|
|
184
|
+
frontendDelivery = true;
|
|
185
|
+
addSignal(input.signals, {
|
|
186
|
+
id: `frontend-delivery-${input.source}`,
|
|
187
|
+
source: input.source,
|
|
188
|
+
message: `${input.source} contains an implementation action and a frontend UI deliverable`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (!negated &&
|
|
192
|
+
!documentationOnly &&
|
|
193
|
+
matchesAny(clause, PATH_SUPPORTED_FRONTEND_DELIVERY_PATTERNS)) {
|
|
194
|
+
pathSupportedFrontendDelivery = true;
|
|
195
|
+
}
|
|
196
|
+
if (!negated &&
|
|
197
|
+
!documentationOnly &&
|
|
198
|
+
matchesAny(clause, CONTEXT_SUPPORTED_FRONTEND_DELIVERY_PATTERNS)) {
|
|
199
|
+
contextSupportedFrontendDelivery = true;
|
|
200
|
+
}
|
|
201
|
+
const backendClause = masksExistingBackendDependency(clause);
|
|
202
|
+
if (!matchesAny(clause, BACKEND_NEGATION_PATTERNS) &&
|
|
203
|
+
matchesAny(backendClause, BACKEND_DELIVERY_PATTERNS)) {
|
|
204
|
+
backendDelivery = true;
|
|
205
|
+
addSignal(input.signals, {
|
|
206
|
+
id: `backend-delivery-${input.source}`,
|
|
207
|
+
source: input.source,
|
|
208
|
+
message: `${input.source} contains an implementation action and a backend deliverable`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
frontendDelivery,
|
|
214
|
+
pathSupportedFrontendDelivery,
|
|
215
|
+
contextSupportedFrontendDelivery,
|
|
216
|
+
backendDelivery,
|
|
217
|
+
frontendNegated,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function normalizedAllowedPath(value) {
|
|
221
|
+
return value.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
222
|
+
}
|
|
223
|
+
function reasonsForClassification(input) {
|
|
224
|
+
const signalIds = input.signals.map((signal) => signal.id);
|
|
225
|
+
const reasons = [`task demand classified as ${input.kind}`];
|
|
226
|
+
if (signalIds.length > 0)
|
|
227
|
+
reasons.push(`signals: ${signalIds.join(", ")}`);
|
|
228
|
+
if (input.blockers.length > 0)
|
|
229
|
+
reasons.push(`blockers: ${input.blockers.join(", ")}`);
|
|
230
|
+
return reasons;
|
|
231
|
+
}
|
|
232
|
+
export function classifyTaskDemand(input) {
|
|
233
|
+
const signals = [];
|
|
234
|
+
const blockers = [];
|
|
235
|
+
const requirement = normalizeTaskRequirementText(input.requirementMarkdown);
|
|
236
|
+
const hasFrontendTaskType = FRONTEND_TASK_TYPE.test(requirement);
|
|
237
|
+
const hasBackendTaskType = BACKEND_TASK_TYPE.test(requirement);
|
|
238
|
+
if (hasFrontendTaskType) {
|
|
239
|
+
addSignal(signals, {
|
|
240
|
+
id: "task-type-frontend-feature",
|
|
241
|
+
source: "requirement",
|
|
242
|
+
message: "requirement contains the materialized frontend-feature task type",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (hasBackendTaskType) {
|
|
246
|
+
addSignal(signals, {
|
|
247
|
+
id: "task-type-backend-feature",
|
|
248
|
+
source: "requirement",
|
|
249
|
+
message: "requirement contains the materialized backend-feature task type",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const titleSignals = collectTextSignals({
|
|
253
|
+
text: input.title,
|
|
254
|
+
source: "title",
|
|
255
|
+
signals,
|
|
256
|
+
});
|
|
257
|
+
const requirementSignals = collectTextSignals({
|
|
258
|
+
text: requirement,
|
|
259
|
+
source: "requirement",
|
|
260
|
+
signals,
|
|
261
|
+
});
|
|
262
|
+
const frontendNegated = titleSignals.frontendNegated || requirementSignals.frontendNegated;
|
|
263
|
+
if (frontendNegated)
|
|
264
|
+
blockers.push("frontend-negated");
|
|
265
|
+
const frontendFramework = input.frontendProject?.framework ?? "unknown";
|
|
266
|
+
const hasStrongFrontendProjectEvidence = frontendFramework === "react" ||
|
|
267
|
+
frontendFramework === "next" ||
|
|
268
|
+
frontendFramework === "vue";
|
|
269
|
+
if (hasStrongFrontendProjectEvidence) {
|
|
270
|
+
addSignal(signals, {
|
|
271
|
+
id: `frontend-project-${frontendFramework}`,
|
|
272
|
+
source: "project",
|
|
273
|
+
message: `project has strong ${frontendFramework} framework evidence`,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
let frontendPath = false;
|
|
277
|
+
let backendPath = false;
|
|
278
|
+
for (const rawPath of input.allowedPaths) {
|
|
279
|
+
const allowedPath = normalizedAllowedPath(rawPath);
|
|
280
|
+
if (!FRONTEND_PATH_EXCLUSIONS.test(allowedPath) &&
|
|
281
|
+
matchesAny(allowedPath, FRONTEND_PATH_PATTERNS)) {
|
|
282
|
+
frontendPath = true;
|
|
283
|
+
}
|
|
284
|
+
if (matchesAny(allowedPath, BACKEND_PATH_PATTERNS))
|
|
285
|
+
backendPath = true;
|
|
286
|
+
}
|
|
287
|
+
if (frontendPath) {
|
|
288
|
+
addSignal(signals, {
|
|
289
|
+
id: "frontend-path",
|
|
290
|
+
source: "allowed-path",
|
|
291
|
+
message: "allowedPaths include a frontend-specific source path",
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (backendPath) {
|
|
295
|
+
addSignal(signals, {
|
|
296
|
+
id: "backend-path",
|
|
297
|
+
source: "allowed-path",
|
|
298
|
+
message: "allowedPaths include a backend-specific source path",
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
const pathSupportedFrontendDelivery = frontendPath &&
|
|
302
|
+
(titleSignals.pathSupportedFrontendDelivery ||
|
|
303
|
+
requirementSignals.pathSupportedFrontendDelivery ||
|
|
304
|
+
titleSignals.contextSupportedFrontendDelivery ||
|
|
305
|
+
requirementSignals.contextSupportedFrontendDelivery);
|
|
306
|
+
if (pathSupportedFrontendDelivery) {
|
|
307
|
+
const supportedByRequirement = requirementSignals.pathSupportedFrontendDelivery ||
|
|
308
|
+
requirementSignals.contextSupportedFrontendDelivery;
|
|
309
|
+
addSignal(signals, {
|
|
310
|
+
id: supportedByRequirement
|
|
311
|
+
? "frontend-path-supported-delivery-requirement"
|
|
312
|
+
: "frontend-path-supported-delivery-title",
|
|
313
|
+
source: supportedByRequirement ? "requirement" : "title",
|
|
314
|
+
message: "task text contains a frontend UI artifact and allowedPaths provide frontend source-path evidence",
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const projectSupportedFrontendDelivery = hasStrongFrontendProjectEvidence &&
|
|
318
|
+
(titleSignals.contextSupportedFrontendDelivery ||
|
|
319
|
+
requirementSignals.contextSupportedFrontendDelivery);
|
|
320
|
+
if (projectSupportedFrontendDelivery) {
|
|
321
|
+
addSignal(signals, {
|
|
322
|
+
id: requirementSignals.contextSupportedFrontendDelivery
|
|
323
|
+
? "frontend-project-supported-delivery-requirement"
|
|
324
|
+
: "frontend-project-supported-delivery-title",
|
|
325
|
+
source: requirementSignals.contextSupportedFrontendDelivery
|
|
326
|
+
? "requirement"
|
|
327
|
+
: "title",
|
|
328
|
+
message: "task text contains user-visible interaction/state delivery and the project has strong frontend framework evidence",
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const frontendDelivery = titleSignals.frontendDelivery ||
|
|
332
|
+
requirementSignals.frontendDelivery ||
|
|
333
|
+
pathSupportedFrontendDelivery ||
|
|
334
|
+
projectSupportedFrontendDelivery;
|
|
335
|
+
const allowedPathsOnlyCoverNonProductArtifacts = input.allowedPaths.length > 0 &&
|
|
336
|
+
input.allowedPaths.every((rawPath) => {
|
|
337
|
+
const allowedPath = normalizedAllowedPath(rawPath);
|
|
338
|
+
return matchesAny(allowedPath, NON_PRODUCT_ALLOWED_PATH_PATTERNS);
|
|
339
|
+
});
|
|
340
|
+
if (allowedPathsOnlyCoverNonProductArtifacts && frontendDelivery) {
|
|
341
|
+
blockers.push("non-product-allowed-paths");
|
|
342
|
+
}
|
|
343
|
+
const eligibleFrontendDelivery = frontendDelivery && !allowedPathsOnlyCoverNonProductArtifacts;
|
|
344
|
+
const backendDelivery = titleSignals.backendDelivery || requirementSignals.backendDelivery;
|
|
345
|
+
const taskTypeConflict = (hasFrontendTaskType && (backendDelivery || frontendNegated)) ||
|
|
346
|
+
(hasBackendTaskType && eligibleFrontendDelivery);
|
|
347
|
+
if (taskTypeConflict)
|
|
348
|
+
blockers.push("task-type-conflict");
|
|
349
|
+
let kind;
|
|
350
|
+
if ((hasFrontendTaskType && hasBackendTaskType) || taskTypeConflict) {
|
|
351
|
+
kind = "mixed";
|
|
352
|
+
}
|
|
353
|
+
else if (hasFrontendTaskType) {
|
|
354
|
+
kind = "frontend";
|
|
355
|
+
}
|
|
356
|
+
else if (hasBackendTaskType) {
|
|
357
|
+
kind = "backend";
|
|
358
|
+
}
|
|
359
|
+
else if (eligibleFrontendDelivery && backendDelivery) {
|
|
360
|
+
kind = "mixed";
|
|
361
|
+
}
|
|
362
|
+
else if (eligibleFrontendDelivery && frontendNegated) {
|
|
363
|
+
kind = "mixed";
|
|
364
|
+
}
|
|
365
|
+
else if (eligibleFrontendDelivery) {
|
|
366
|
+
kind = "frontend";
|
|
367
|
+
}
|
|
368
|
+
else if (backendDelivery) {
|
|
369
|
+
kind = "backend";
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
kind = "unknown";
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
kind,
|
|
376
|
+
signals,
|
|
377
|
+
blockers,
|
|
378
|
+
reasons: reasonsForClassification({ kind, signals, blockers }),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
export function resolveTaskDagTemplateSelection(input) {
|
|
382
|
+
const requestedTemplate = input.requestedTemplate ?? "standard-dag";
|
|
383
|
+
const taskKindTemplate = SPECIAL_TEMPLATE_BY_TASK_KIND[input.taskKind];
|
|
384
|
+
if (taskKindTemplate) {
|
|
385
|
+
return {
|
|
386
|
+
template: taskKindTemplate,
|
|
387
|
+
source: "taskKind",
|
|
388
|
+
reasons: [TASK_KIND_ROUTING_REASON[input.taskKind]],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (SPECIAL_REQUESTED_TEMPLATES.has(requestedTemplate)) {
|
|
392
|
+
return {
|
|
393
|
+
template: requestedTemplate,
|
|
394
|
+
source: "requested-template",
|
|
395
|
+
reasons: [],
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (input.taskKind !== "standard") {
|
|
399
|
+
return {
|
|
400
|
+
template: requestedTemplate,
|
|
401
|
+
source: "requested-template",
|
|
402
|
+
reasons: [],
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (requestedTemplate !== "standard-dag") {
|
|
406
|
+
return {
|
|
407
|
+
template: requestedTemplate,
|
|
408
|
+
source: "requested-template",
|
|
409
|
+
reasons: [],
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const classification = classifyTaskDemand(input);
|
|
413
|
+
if (classification.kind === "frontend") {
|
|
414
|
+
const frontendSignals = classification.signals
|
|
415
|
+
.map((signal) => signal.id)
|
|
416
|
+
.filter((id) => id === "task-type-frontend-feature" || id.startsWith("frontend-"));
|
|
417
|
+
return {
|
|
418
|
+
template: "frontend-implementation",
|
|
419
|
+
source: "task-source",
|
|
420
|
+
reasons: [
|
|
421
|
+
`task source classified as frontend from signals: ${frontendSignals.join(", ")}`,
|
|
422
|
+
],
|
|
423
|
+
classification,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
template: requestedTemplate,
|
|
428
|
+
source: "requested-template",
|
|
429
|
+
reasons: [],
|
|
430
|
+
classification,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
@@ -41,6 +41,7 @@ export function openConsoleAppData(options) {
|
|
|
41
41
|
interviews: path.join(repoNamespace, "interviews"),
|
|
42
42
|
chats: path.join(repoNamespace, "chats"),
|
|
43
43
|
chatSessions: path.join(repoNamespace, "chat-sessions"),
|
|
44
|
+
chatEvents: path.join(repoNamespace, "chat-events"),
|
|
44
45
|
};
|
|
45
46
|
for (const dir of [
|
|
46
47
|
paths.drafts,
|
|
@@ -51,6 +52,7 @@ export function openConsoleAppData(options) {
|
|
|
51
52
|
paths.interviews,
|
|
52
53
|
paths.chats,
|
|
53
54
|
paths.chatSessions,
|
|
55
|
+
paths.chatEvents,
|
|
54
56
|
]) {
|
|
55
57
|
ensureSecureDir(dir);
|
|
56
58
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const ARTIFACT_KINDS = ["operation", "contract", "dag", "report", "html"];
|
|
2
|
+
export function artifactFromEventPayload(payload) {
|
|
3
|
+
const raw = payload.artifact;
|
|
4
|
+
if (!raw || typeof raw !== "object")
|
|
5
|
+
return undefined;
|
|
6
|
+
const candidate = raw;
|
|
7
|
+
if (candidate.schemaVersion !== 1 ||
|
|
8
|
+
typeof candidate.artifactId !== "string" || !candidate.artifactId ||
|
|
9
|
+
typeof candidate.kind !== "string" || !ARTIFACT_KINDS.includes(candidate.kind) ||
|
|
10
|
+
typeof candidate.previewHash !== "string")
|
|
11
|
+
return undefined;
|
|
12
|
+
for (const field of ["title", "summary", "htmlPreview", "reportText", "inspectHref"]) {
|
|
13
|
+
if (candidate[field] !== undefined && typeof candidate[field] !== "string")
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return candidate;
|
|
17
|
+
}
|
|
18
|
+
export function mergeArtifactCardState(current, incoming) {
|
|
19
|
+
const index = current.findIndex((item) => item.artifactId === incoming.artifactId);
|
|
20
|
+
if (index < 0)
|
|
21
|
+
return [...current, incoming];
|
|
22
|
+
return current.map((item, candidate) => candidate === index ? { ...item, ...incoming } : item);
|
|
23
|
+
}
|