parallel-codex-tui 0.1.3 → 0.1.5

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.
Files changed (78) hide show
  1. package/.parallel-codex/config.example.toml +136 -3
  2. package/README.md +299 -21
  3. package/dist/bootstrap.js +37 -17
  4. package/dist/cli-args.js +34 -3
  5. package/dist/cli-help.js +20 -0
  6. package/dist/cli-startup-preflight.js +18 -0
  7. package/dist/cli-startup-recovery.js +82 -0
  8. package/dist/cli-workspace-picker.js +330 -0
  9. package/dist/cli-workspace-transition.js +33 -0
  10. package/dist/cli-workspace.js +7 -71
  11. package/dist/cli.js +234 -24
  12. package/dist/core/collaboration-timeline.js +268 -0
  13. package/dist/core/config-errors.js +14 -0
  14. package/dist/core/config.js +297 -109
  15. package/dist/core/file-store.js +119 -6
  16. package/dist/core/lease-finalization.js +22 -0
  17. package/dist/core/paths.js +7 -0
  18. package/dist/core/process-identity.js +48 -0
  19. package/dist/core/process-mutation-turn.js +128 -0
  20. package/dist/core/process-ownership.js +276 -0
  21. package/dist/core/process-tree.js +90 -0
  22. package/dist/core/router-audit.js +155 -0
  23. package/dist/core/router-redaction.js +31 -0
  24. package/dist/core/router.js +462 -35
  25. package/dist/core/session-index.js +412 -88
  26. package/dist/core/session-manager.js +1110 -40
  27. package/dist/core/task-session-details.js +175 -0
  28. package/dist/core/task-state-machine.js +18 -0
  29. package/dist/core/workspace-commit-recovery.js +118 -0
  30. package/dist/core/workspace.js +19 -11
  31. package/dist/doctor.js +373 -34
  32. package/dist/domain/schemas.js +142 -7
  33. package/dist/orchestrator/collaboration-channel.js +289 -6
  34. package/dist/orchestrator/feature-plan.js +70 -0
  35. package/dist/orchestrator/final-acceptance.js +86 -0
  36. package/dist/orchestrator/judge-artifacts.js +236 -0
  37. package/dist/orchestrator/orchestrator.js +2086 -203
  38. package/dist/orchestrator/prompts.js +168 -5
  39. package/dist/orchestrator/supervisor-summary.js +56 -2
  40. package/dist/orchestrator/workspace-sandbox.js +927 -0
  41. package/dist/tui/App.js +3187 -161
  42. package/dist/tui/AppShell.js +196 -25
  43. package/dist/tui/CollaborationTimelineView.js +327 -0
  44. package/dist/tui/FeatureBoardView.js +232 -0
  45. package/dist/tui/InputBar.js +581 -57
  46. package/dist/tui/RouterDiagnosticsView.js +469 -0
  47. package/dist/tui/StatusBar.js +610 -57
  48. package/dist/tui/StatusDetailView.js +164 -0
  49. package/dist/tui/TaskSessionDetailView.js +222 -0
  50. package/dist/tui/TaskSessionsView.js +210 -0
  51. package/dist/tui/TerminalOutput.js +53 -9
  52. package/dist/tui/WorkerOutputView.js +1404 -161
  53. package/dist/tui/WorkerOverviewView.js +269 -0
  54. package/dist/tui/chat-history.js +25 -0
  55. package/dist/tui/chat-input.js +67 -19
  56. package/dist/tui/chat-paste.js +76 -0
  57. package/dist/tui/display-width.js +41 -3
  58. package/dist/tui/incremental-text-file.js +101 -0
  59. package/dist/tui/keyboard.js +49 -0
  60. package/dist/tui/markdown-text.js +14 -0
  61. package/dist/tui/raw-input-decoder.js +3 -0
  62. package/dist/tui/scrolling.js +2 -1
  63. package/dist/tui/status-line.js +360 -11
  64. package/dist/tui/task-memory.js +13 -1
  65. package/dist/tui/task-result.js +105 -0
  66. package/dist/tui/terminal-screen.js +13 -1
  67. package/dist/tui/theme-contrast.js +144 -0
  68. package/dist/tui/theme-preview.js +109 -0
  69. package/dist/tui/theme.js +158 -0
  70. package/dist/version.js +1 -1
  71. package/dist/workers/capabilities.js +213 -0
  72. package/dist/workers/live-probe.js +177 -0
  73. package/dist/workers/mock-adapter.js +73 -8
  74. package/dist/workers/native-attach.js +106 -16
  75. package/dist/workers/process-adapter.js +572 -77
  76. package/dist/workers/provider.js +26 -0
  77. package/dist/workers/registry.js +12 -20
  78. package/package.json +11 -2
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+ const FinalAcceptanceItemSchema = z.object({
3
+ criterion_id: z.string().regex(/^A-\d{1,4}$/),
4
+ status: z.enum(["passed", "failed"]),
5
+ evidence: z.string().trim().min(1).max(4000)
6
+ }).strict();
7
+ export const FinalJudgeAcceptanceSchema = z.object({
8
+ version: z.literal(1),
9
+ decision: z.enum(["approved", "rejected"]),
10
+ summary: z.string().trim().min(1).max(4000),
11
+ acceptance: z.array(FinalAcceptanceItemSchema).min(1).max(100),
12
+ changed_paths: z.array(z.string().trim().min(1).max(1000)).max(10000)
13
+ }).strict();
14
+ export function validateFinalJudgeAcceptance(input, expectedCriterionIds, expectedChangedPaths) {
15
+ const parsed = FinalJudgeAcceptanceSchema.safeParse(input);
16
+ if (!parsed.success) {
17
+ return {
18
+ acceptance: null,
19
+ report: {
20
+ version: 1,
21
+ state: "invalid",
22
+ decision: "unknown",
23
+ issues: parsed.error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`)
24
+ }
25
+ };
26
+ }
27
+ const acceptance = parsed.data;
28
+ const issues = [];
29
+ const actualIds = acceptance.acceptance.map((item) => item.criterion_id);
30
+ const duplicateIds = repeated(actualIds);
31
+ if (duplicateIds.length > 0) {
32
+ issues.push(`duplicate acceptance criteria: ${duplicateIds.join(", ")}`);
33
+ }
34
+ const expectedIds = [...new Set(expectedCriterionIds)].sort();
35
+ const uniqueActualIds = [...new Set(actualIds)].sort();
36
+ const missingIds = expectedIds.filter((id) => !uniqueActualIds.includes(id));
37
+ const unknownIds = uniqueActualIds.filter((id) => !expectedIds.includes(id));
38
+ if (missingIds.length > 0) {
39
+ issues.push(`missing acceptance criteria: ${missingIds.join(", ")}`);
40
+ }
41
+ if (unknownIds.length > 0) {
42
+ issues.push(`unknown acceptance criteria: ${unknownIds.join(", ")}`);
43
+ }
44
+ const failedIds = acceptance.acceptance
45
+ .filter((item) => item.status === "failed")
46
+ .map((item) => item.criterion_id);
47
+ if (acceptance.decision === "approved" && failedIds.length > 0) {
48
+ issues.push(`approved decision contains failed criteria: ${failedIds.join(", ")}`);
49
+ }
50
+ if (acceptance.decision === "rejected" && failedIds.length === 0) {
51
+ issues.push("rejected decision must identify at least one failed criterion");
52
+ }
53
+ const expectedPaths = [...new Set(expectedChangedPaths)].sort();
54
+ const actualPaths = [...new Set(acceptance.changed_paths)].sort();
55
+ if (acceptance.changed_paths.length !== actualPaths.length) {
56
+ issues.push(`duplicate changed paths: ${repeated(acceptance.changed_paths).join(", ")}`);
57
+ }
58
+ const missingPaths = expectedPaths.filter((path) => !actualPaths.includes(path));
59
+ const unknownPaths = actualPaths.filter((path) => !expectedPaths.includes(path));
60
+ if (missingPaths.length > 0) {
61
+ issues.push(`missing changed paths: ${missingPaths.join(", ")}`);
62
+ }
63
+ if (unknownPaths.length > 0) {
64
+ issues.push(`unknown changed paths: ${unknownPaths.join(", ")}`);
65
+ }
66
+ return {
67
+ acceptance,
68
+ report: {
69
+ version: 1,
70
+ state: issues.length === 0 ? "valid" : "invalid",
71
+ decision: acceptance.decision,
72
+ issues
73
+ }
74
+ };
75
+ }
76
+ function repeated(values) {
77
+ const seen = new Set();
78
+ const duplicates = new Set();
79
+ for (const value of values) {
80
+ if (seen.has(value)) {
81
+ duplicates.add(value);
82
+ }
83
+ seen.add(value);
84
+ }
85
+ return [...duplicates].sort();
86
+ }
@@ -0,0 +1,236 @@
1
+ import { Lexer } from "marked";
2
+ export const JUDGE_REQUIRED_ARTIFACTS = [
3
+ "requirements.md",
4
+ "plan.md",
5
+ "acceptance.md",
6
+ "actor-brief.md",
7
+ "critic-brief.md"
8
+ ];
9
+ export const JUDGE_VALIDATION_FILE = "judge-validation.json";
10
+ const CONTRACT_ARTIFACTS = {
11
+ "requirements.md": { prefix: "R", label: "requirement" },
12
+ "plan.md": { prefix: "P", label: "plan step" },
13
+ "acceptance.md": { prefix: "A", label: "acceptance criterion" }
14
+ };
15
+ const PLACEHOLDER_PATTERN = /^(?:todo|tbd|pending|placeholder|to be determined|coming soon|n\/?a|none|待定|稍后(?:补充|处理)?|后续(?:补充|再说)?|暂无|未定|占位(?:内容)?)(?:\s*[::-].*)?$/iu;
16
+ const ARTIFACT_NAME_PATTERN = /^(?:requirements|plan|acceptance|actor-brief|critic-brief)\.md$/iu;
17
+ export function validateJudgeArtifacts(input) {
18
+ const requirements = validateContractArtifact("requirements.md", input["requirements.md"] ?? "");
19
+ const plan = validateContractArtifact("plan.md", input["plan.md"] ?? "");
20
+ const acceptance = validateContractArtifact("acceptance.md", input["acceptance.md"] ?? "");
21
+ const actorBrief = validateBriefArtifact("actor-brief.md", input["actor-brief.md"] ?? "");
22
+ const criticBrief = validateBriefArtifact("critic-brief.md", input["critic-brief.md"] ?? "");
23
+ const requirementIds = new Set(requirements.items.map((item) => item.id));
24
+ for (const item of acceptance.items) {
25
+ const unknown = item.references.filter((reference) => !requirementIds.has(reference));
26
+ if (unknown.length > 0) {
27
+ acceptance.validation.issues.push(issue("acceptance.md", "unknown_requirement_reference", `${item.id} references unknown requirement${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}.`));
28
+ acceptance.validation.state = "invalid";
29
+ }
30
+ }
31
+ const artifacts = {
32
+ "requirements.md": requirements.validation,
33
+ "plan.md": plan.validation,
34
+ "acceptance.md": acceptance.validation,
35
+ "actor-brief.md": actorBrief.validation,
36
+ "critic-brief.md": criticBrief.validation
37
+ };
38
+ const issues = JUDGE_REQUIRED_ARTIFACTS.flatMap((file) => artifacts[file].issues);
39
+ return {
40
+ version: 1,
41
+ state: issues.length === 0 ? "valid" : "invalid",
42
+ artifacts,
43
+ contract: {
44
+ requirements: requirements.items,
45
+ plan: plan.items,
46
+ acceptance: acceptance.items
47
+ },
48
+ briefs: {
49
+ actor: actorBrief.content,
50
+ critic: criticBrief.content
51
+ },
52
+ issues
53
+ };
54
+ }
55
+ function validateContractArtifact(file, markdown) {
56
+ const parsed = parseMarkdown(markdown);
57
+ const issues = [];
58
+ const spec = CONTRACT_ARTIFACTS[file];
59
+ if (!markdown.trim()) {
60
+ issues.push(issue(file, "missing", `${file} is missing or empty.`));
61
+ }
62
+ else if (parsed.parseError) {
63
+ issues.push(issue(file, "parse_error", `${file} is not valid Markdown: ${parsed.parseError}`));
64
+ }
65
+ const normalized = parsed.listItems.map((text, index) => normalizeContractItem(text, spec.prefix, index));
66
+ const meaningful = normalized.filter((item) => isMeaningful(item.text));
67
+ const placeholders = normalized.filter((item) => !isMeaningful(item.text));
68
+ if (markdown.trim() && !parsed.parseError && parsed.listItems.length === 0) {
69
+ issues.push(issue(file, "missing_list_items", `${file} must contain at least one Markdown list ${spec.label}.`));
70
+ }
71
+ else if (parsed.listItems.length > 0 && meaningful.length === 0) {
72
+ issues.push(issue(file, "placeholder_only", `${file} contains only placeholder ${spec.label}s.`));
73
+ }
74
+ else if (placeholders.length > 0) {
75
+ issues.push(issue(file, "placeholder_items", `${file} still contains ${placeholders.length} placeholder ${spec.label}${placeholders.length === 1 ? "" : "s"}.`));
76
+ }
77
+ const duplicateIds = repeatedIds(meaningful);
78
+ if (duplicateIds.length > 0) {
79
+ issues.push(issue(file, "duplicate_item_id", `${file} repeats item id${duplicateIds.length === 1 ? "" : "s"}: ${duplicateIds.join(", ")}.`));
80
+ }
81
+ return {
82
+ validation: {
83
+ state: issues.length === 0 ? "valid" : "invalid",
84
+ item_count: parsed.listItems.length,
85
+ content_count: parsed.content.length,
86
+ issues
87
+ },
88
+ items: meaningful
89
+ };
90
+ }
91
+ function validateBriefArtifact(file, markdown) {
92
+ const parsed = parseMarkdown(markdown);
93
+ const issues = [];
94
+ const meaningful = parsed.content.filter(isMeaningful);
95
+ const placeholders = parsed.content.filter((content) => !isMeaningful(content));
96
+ if (!markdown.trim()) {
97
+ issues.push(issue(file, "missing", `${file} is missing or empty.`));
98
+ }
99
+ else if (parsed.parseError) {
100
+ issues.push(issue(file, "parse_error", `${file} is not valid Markdown: ${parsed.parseError}`));
101
+ }
102
+ else if (parsed.content.length === 0) {
103
+ issues.push(issue(file, "missing_content", `${file} must contain implementation guidance below its heading.`));
104
+ }
105
+ else if (meaningful.length === 0) {
106
+ issues.push(issue(file, "placeholder_only", `${file} contains only placeholder guidance.`));
107
+ }
108
+ else if (placeholders.length > 0) {
109
+ issues.push(issue(file, "placeholder_items", `${file} still contains placeholder guidance.`));
110
+ }
111
+ return {
112
+ validation: {
113
+ state: issues.length === 0 ? "valid" : "invalid",
114
+ item_count: parsed.listItems.length,
115
+ content_count: parsed.content.length,
116
+ issues
117
+ },
118
+ content: meaningful
119
+ };
120
+ }
121
+ function parseMarkdown(markdown) {
122
+ if (!markdown.trim()) {
123
+ return { listItems: [], content: [] };
124
+ }
125
+ try {
126
+ const tokens = Lexer.lex(markdown);
127
+ return {
128
+ listItems: collectListItems(tokens),
129
+ content: collectContent(tokens)
130
+ };
131
+ }
132
+ catch (error) {
133
+ return {
134
+ listItems: [],
135
+ content: [],
136
+ parseError: error instanceof Error ? error.message : String(error)
137
+ };
138
+ }
139
+ }
140
+ function collectListItems(tokens) {
141
+ const items = [];
142
+ for (const token of tokens) {
143
+ if (isListToken(token)) {
144
+ for (const item of token.items) {
145
+ const text = normalizeWhitespace(item.tokens
146
+ .filter((itemToken) => itemToken.type !== "list")
147
+ .map(tokenPlainText)
148
+ .join(" "));
149
+ items.push(text);
150
+ items.push(...collectListItems(item.tokens.filter(isListToken)));
151
+ }
152
+ continue;
153
+ }
154
+ const nested = tokenTokens(token);
155
+ if (nested.length > 0) {
156
+ items.push(...collectListItems(nested));
157
+ }
158
+ }
159
+ return items;
160
+ }
161
+ function collectContent(tokens) {
162
+ const content = [];
163
+ for (const token of tokens) {
164
+ if (token.type === "heading" || token.type === "space" || token.type === "hr" || token.type === "def") {
165
+ continue;
166
+ }
167
+ if (isListToken(token)) {
168
+ content.push(...collectListItems([token]));
169
+ continue;
170
+ }
171
+ if (token.type === "blockquote") {
172
+ content.push(...collectContent(tokenTokens(token)));
173
+ continue;
174
+ }
175
+ const text = normalizeWhitespace(tokenPlainText(token));
176
+ if (text) {
177
+ content.push(text);
178
+ }
179
+ }
180
+ return content;
181
+ }
182
+ function normalizeContractItem(text, prefix, index) {
183
+ const idPattern = new RegExp(`^\\[?(${prefix}-\\d{1,4})\\]?\\s*(?:[::-]\\s*)?`, "iu");
184
+ const match = text.match(idPattern);
185
+ const id = match?.[1]?.toUpperCase() ?? `${prefix}-${String(index + 1).padStart(3, "0")}`;
186
+ const normalizedText = normalizeWhitespace(match ? text.slice(match[0].length) : text);
187
+ const references = Array.from(normalizedText.matchAll(/\bR-\d{1,4}\b/giu), (reference) => reference[0].toUpperCase());
188
+ return {
189
+ id,
190
+ text: normalizedText,
191
+ references: [...new Set(references)]
192
+ };
193
+ }
194
+ function tokenPlainText(token) {
195
+ if (token.type === "checkbox" || token.type === "space" || token.type === "def") {
196
+ return "";
197
+ }
198
+ const nested = tokenTokens(token);
199
+ if (nested.length > 0) {
200
+ return nested.map(tokenPlainText).join(" ");
201
+ }
202
+ if ("text" in token && typeof token.text === "string") {
203
+ return token.text;
204
+ }
205
+ return typeof token.raw === "string" ? token.raw : "";
206
+ }
207
+ function tokenTokens(token) {
208
+ return "tokens" in token && Array.isArray(token.tokens) ? token.tokens : [];
209
+ }
210
+ function isListToken(token) {
211
+ return token.type === "list" && "items" in token && Array.isArray(token.items);
212
+ }
213
+ function normalizeWhitespace(value) {
214
+ return value.replace(/\s+/gu, " ").trim();
215
+ }
216
+ function isMeaningful(value) {
217
+ const normalized = normalizeWhitespace(value).replace(/[.。,,;;::!!??]+$/gu, "").trim();
218
+ return Boolean(normalized)
219
+ && /[\p{L}\p{N}]/u.test(normalized)
220
+ && !PLACEHOLDER_PATTERN.test(normalized)
221
+ && !ARTIFACT_NAME_PATTERN.test(normalized);
222
+ }
223
+ function repeatedIds(items) {
224
+ const seen = new Set();
225
+ const duplicates = new Set();
226
+ for (const item of items) {
227
+ if (seen.has(item.id)) {
228
+ duplicates.add(item.id);
229
+ }
230
+ seen.add(item.id);
231
+ }
232
+ return [...duplicates];
233
+ }
234
+ function issue(file, code, message) {
235
+ return { file, code, message };
236
+ }