prizmkit 1.1.62 → 1.1.64

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.
@@ -73,6 +73,9 @@ class ProgressTracker:
73
73
  self.last_text_snippet = ""
74
74
  self.is_active = True
75
75
  self.errors = []
76
+ self.event_format = ""
77
+ self.active_subagent_count = 0
78
+ self.subagent_status_counts = Counter()
76
79
  self._text_buffer = ""
77
80
  self._in_tool_use = False
78
81
  self._current_tool_input_parts = []
@@ -87,8 +90,72 @@ class ProgressTracker:
87
90
  """
88
91
  event_type = event.get("type", "")
89
92
 
93
+ # ── Codex exec --json JSONL format ──────────────────────────
94
+ if event_type in (
95
+ "thread.started", "turn.started", "turn.completed",
96
+ "turn.failed", "item.started", "item.completed", "error",
97
+ ):
98
+ self.event_format = "codex-json"
99
+ self.is_active = True
100
+
101
+ if event_type == "turn.started":
102
+ self.message_count += 1
103
+
104
+ elif event_type in ("item.started", "item.completed"):
105
+ item = event.get("item", {})
106
+ item_type = item.get("type", "")
107
+
108
+ if item_type == "agent_message":
109
+ text = item.get("text", "")
110
+ if text.strip():
111
+ self.last_text_snippet = text.strip()[:120]
112
+ self._detect_phase(text)
113
+
114
+ elif item_type == "collab_tool_call":
115
+ tool_name = item.get("tool", "collab")
116
+ if event_type == "item.started":
117
+ self.current_tool = tool_name
118
+ self.tool_call_counts[tool_name] += 1
119
+ self.total_tool_calls += 1
120
+ elif item.get("status") == "completed":
121
+ self.current_tool = None
122
+ self._extract_tool_summary_from_dict(item)
123
+ self._update_subagent_status_counts(
124
+ item.get("agents_states", {})
125
+ )
126
+
127
+ prompt = item.get("prompt")
128
+ if prompt:
129
+ self._detect_phase(prompt)
130
+
131
+ else:
132
+ tool_name = item.get("tool") or item.get("name")
133
+ if tool_name:
134
+ if event_type == "item.started":
135
+ self.current_tool = tool_name
136
+ self.tool_call_counts[tool_name] += 1
137
+ self.total_tool_calls += 1
138
+ elif item.get("status") == "completed":
139
+ self.current_tool = None
140
+ self._extract_tool_summary_from_dict(item)
141
+
142
+ elif event_type == "turn.completed":
143
+ self.current_tool = None
144
+
145
+ elif event_type == "turn.failed":
146
+ error = event.get("error") or event.get("message") or "Codex turn failed"
147
+ self.errors.append(str(error))
148
+ self.current_tool = None
149
+
150
+ elif event_type == "error":
151
+ error = event.get("error") or event.get("message") or "Unknown error"
152
+ self.errors.append(str(error))
153
+
154
+ return
155
+
90
156
  # ── Claude Code verbose format ──────────────────────────────
91
157
  if event_type == "assistant":
158
+ self.event_format = self.event_format or "stream-json"
92
159
  self.message_count += 1
93
160
  self.is_active = True
94
161
  message = event.get("message", {})
@@ -113,16 +180,19 @@ class ProgressTracker:
113
180
 
114
181
  elif event_type == "tool_result" or event_type == "user":
115
182
  # tool_result contains output from tool execution
183
+ self.event_format = self.event_format or "stream-json"
116
184
  self.is_active = True
117
185
 
118
186
  elif event_type == "system":
119
187
  # System events (hooks, init, etc.) — track but don't count as messages
188
+ self.event_format = self.event_format or "stream-json"
120
189
  subtype = event.get("subtype", "")
121
190
  if subtype == "init":
122
191
  self.is_active = True
123
192
 
124
193
  # ── Claude API raw stream format ────────────────────────────
125
194
  elif event_type == "message_start":
195
+ self.event_format = self.event_format or "stream-json"
126
196
  self.message_count += 1
127
197
  self.is_active = True
128
198
 
@@ -256,14 +326,38 @@ class ProgressTracker:
256
326
  elif "prompt" in data:
257
327
  self.current_tool_input_summary = str(data["prompt"])[:100]
258
328
 
329
+ def _update_subagent_status_counts(self, agents_states):
330
+ """Track Codex subagent state counts from collab_tool_call items."""
331
+ counts = Counter()
332
+ active = 0
333
+ if isinstance(agents_states, dict):
334
+ for state in agents_states.values():
335
+ if not isinstance(state, dict):
336
+ continue
337
+ status = str(state.get("status", "unknown"))
338
+ counts[status] += 1
339
+ if status not in ("completed", "failed", "cancelled", "canceled"):
340
+ active += 1
341
+ message = state.get("message")
342
+ if message:
343
+ self.last_text_snippet = str(message).strip()[:120]
344
+ self._detect_phase(str(message))
345
+ self.subagent_status_counts = counts
346
+ self.active_subagent_count = active
347
+
259
348
  def to_dict(self):
260
349
  """Export current state as a dictionary for JSON serialization."""
261
350
  tool_calls = [
262
351
  {"name": name, "count": count}
263
352
  for name, count in self.tool_call_counts.most_common()
264
353
  ]
354
+ subagent_states = [
355
+ {"status": status, "count": count}
356
+ for status, count in self.subagent_status_counts.most_common()
357
+ ]
265
358
  return {
266
359
  "updated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
360
+ "event_format": self.event_format,
267
361
  "message_count": self.message_count,
268
362
  "current_tool": self.current_tool,
269
363
  "current_tool_input_summary": self.current_tool_input_summary,
@@ -271,6 +365,8 @@ class ProgressTracker:
271
365
  "detected_phases": self.detected_phases,
272
366
  "tool_calls": tool_calls,
273
367
  "total_tool_calls": self.total_tool_calls,
368
+ "active_subagent_count": self.active_subagent_count,
369
+ "subagent_states": subagent_states,
274
370
  "last_text_snippet": self.last_text_snippet,
275
371
  "is_active": self.is_active,
276
372
  "errors": self.errors[-10:], # Keep last 10 errors
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.62",
2
+ "version": "1.1.64",
3
3
  "skills": {
4
4
  "prizm-kit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -29,6 +29,11 @@ This project uses PrizmKit with the Prizm documentation system for AI-optimized
29
29
  2. Generate a new L2 `.prizm` file following Prizm specification
30
30
  3. Add a pointer in the parent L1 doc's SUBDIRS section
31
31
 
32
+ ### Framework Source Parity
33
+ - When working in the PrizmKit framework source, `dev-pipeline/` and `dev-pipeline-windows/` are paired runtimes. Any runtime, prompt, schema, reset/recovery, timeout, heartbeat, logging, or AI CLI behavior change in one must be mirrored in the other, or explicitly documented as platform-specific.
34
+ - When working in the PrizmKit framework source, `core/skills/orchestration-skill/` and `core/skills/orchestration-skill-windows/` are paired skill sources. Any planner, workflow, launcher, prompt, or documentation behavior change in one must be mirrored in the other, or explicitly documented as platform-specific.
35
+ - Before finishing framework work, run `git diff --name-only` and verify these paired Unix/Windows paths were updated together.
36
+
32
37
  ### Available Commands
33
38
  Run `/prizm-kit` to see all available PrizmKit commands.
34
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.1.62",
3
+ "version": "1.1.64",
4
4
  "description": "Create a new PrizmKit-powered project with clean initialization — no framework dev files, just what you need.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/scaffold.js CHANGED
@@ -33,6 +33,23 @@ import { normalizeRuntime, runtimeLabel, RUNTIME_UNIX, RUNTIME_WINDOWS } from '.
33
33
  const __scaffoldDirname = dirname(fileURLToPath(import.meta.url));
34
34
  const scaffoldPkg = JSON.parse(readFileSync(join(__scaffoldDirname, '..', 'package.json'), 'utf-8'));
35
35
  const PIPELINE_INSTALL_EXCLUDE = new Set(['tests', 'docs', '__pycache__', 'node_modules', '.DS_Store']);
36
+ const PIPELINE_MANAGED_DIRS = new Set(['assets', 'lib', 'scripts', 'templates', 'tests', 'docs']);
37
+ const PIPELINE_RUNTIME_PRESERVE_DIRS = new Set([
38
+ 'state',
39
+ 'logs',
40
+ 'runs',
41
+ 'sessions',
42
+ 'tmp',
43
+ 'cache',
44
+ 'node_modules',
45
+ 'venv',
46
+ '__pycache__',
47
+ ]);
48
+ const PIPELINE_MANAGED_TOP_LEVEL_PATTERNS = [
49
+ /^(run|launch|reset|retry)-.+\.(sh|ps1)$/i,
50
+ /^README\.md$/i,
51
+ /^SCHEMA_ANALYSIS\.md$/i,
52
+ ];
36
53
 
37
54
  // ============================================================
38
55
  // Adapter 动态加载
@@ -159,6 +176,7 @@ export async function installSkills(platform, skills, projectRoot, dryRun, runti
159
176
  continue;
160
177
  }
161
178
 
179
+ await fs.remove(targetDir);
162
180
  await fs.ensureDir(targetDir);
163
181
 
164
182
  // 读取并写入 SKILL.md(CodeBuddy 格式基本透传)
@@ -189,17 +207,18 @@ export async function installSkills(platform, skills, projectRoot, dryRun, runti
189
207
  // so Claude Code shows it as /skillName (not /skillName:skillName).
190
208
  // Assets/scripts are copied into a subdirectory for reference.
191
209
  const commandsDir = path.join(projectRoot, '.claude', 'commands');
210
+ const assetTargetDir = path.join(projectRoot, '.claude', 'command-assets', skillName);
192
211
  if (dryRun) {
193
212
  console.log(chalk.gray(` [dry-run] .claude/commands/${skillName}.md`));
194
213
  continue;
195
214
  }
196
215
  await fs.ensureDir(commandsDir);
197
216
  await fs.writeFile(path.join(commandsDir, `${skillName}.md`), converted);
217
+ await fs.remove(assetTargetDir);
198
218
 
199
219
  if (skillSubdirs.length > 0) {
200
220
  // Place subdirectories outside .claude/commands/ to prevent Claude Code
201
221
  // from registering them as slash commands (e.g. /skillName:assets:file).
202
- const assetTargetDir = path.join(projectRoot, '.claude', 'command-assets', skillName);
203
222
  await fs.ensureDir(assetTargetDir);
204
223
  for (const subdir of skillSubdirs) {
205
224
  await fs.copy(path.join(corePath, subdir), path.join(assetTargetDir, subdir));
@@ -216,6 +235,7 @@ export async function installSkills(platform, skills, projectRoot, dryRun, runti
216
235
  continue;
217
236
  }
218
237
 
238
+ await fs.remove(targetDir);
219
239
  await fs.ensureDir(targetDir);
220
240
  await fs.writeFile(path.join(targetDir, 'SKILL.md'), converted);
221
241
 
@@ -556,6 +576,7 @@ project_doc_fallback_filenames = ["CLAUDE.md", "CODEBUDDY.md"]
556
576
 
557
577
  [agents]
558
578
  max_depth = 1
579
+ job_max_runtime_seconds = 840
559
580
  `;
560
581
  await fs.writeFile(configPath, configToml);
561
582
  await fs.remove(legacySettingsPath);
@@ -922,6 +943,101 @@ export function resolvePipelineFileList(runtime = 'unix') {
922
943
  return collectInstallablePipelineFiles(pipelineSource);
923
944
  }
924
945
 
946
+ function normalizePipelineRelPath(relativeFile) {
947
+ return relativeFile.split(path.sep).join('/');
948
+ }
949
+
950
+ function isPreservedPipelineRuntimeFile(relativeFile) {
951
+ const normalized = normalizePipelineRelPath(relativeFile);
952
+ const segments = normalized.split('/');
953
+ if (segments.some(segment => !segment || segment === '..')) return true;
954
+ if (segments.some(segment => segment.startsWith('.'))) return true;
955
+ return segments.some(segment => PIPELINE_RUNTIME_PRESERVE_DIRS.has(segment));
956
+ }
957
+
958
+ function isLikelyManagedPipelineFile(relativeFile) {
959
+ const normalized = normalizePipelineRelPath(relativeFile);
960
+ const segments = normalized.split('/');
961
+ if (segments.length === 0 || isPreservedPipelineRuntimeFile(normalized)) return false;
962
+ if (segments.length > 1 && PIPELINE_MANAGED_DIRS.has(segments[0])) return true;
963
+ return PIPELINE_MANAGED_TOP_LEVEL_PATTERNS.some(pattern => pattern.test(normalized));
964
+ }
965
+
966
+ async function collectTargetPipelineFiles(dir, base = '') {
967
+ if (!await fs.pathExists(dir)) return [];
968
+ const entries = await fs.readdir(dir, { withFileTypes: true });
969
+ const files = [];
970
+ for (const entry of entries) {
971
+ const rel = base ? path.join(base, entry.name) : entry.name;
972
+ const fullPath = path.join(dir, entry.name);
973
+ if (entry.isDirectory()) {
974
+ files.push(...await collectTargetPipelineFiles(fullPath, rel));
975
+ } else if (entry.isFile()) {
976
+ files.push(rel);
977
+ }
978
+ }
979
+ return files;
980
+ }
981
+
982
+ /**
983
+ * Find PrizmKit-owned pipeline files present in the target install that no
984
+ * longer exist in the framework source for the selected runtime.
985
+ *
986
+ * This intentionally avoids arbitrary top-level files and runtime/state
987
+ * directories so user scratch files are preserved.
988
+ */
989
+ export async function findStaleManagedPipelineFiles(projectRoot, runtime = 'unix') {
990
+ const pipelineTarget = path.join(projectRoot, '.prizmkit', 'dev-pipeline');
991
+ if (!await fs.pathExists(pipelineTarget)) return [];
992
+
993
+ const currentFiles = new Set(
994
+ resolvePipelineFileList(runtime).map(file => normalizePipelineRelPath(file)),
995
+ );
996
+ const targetFiles = await collectTargetPipelineFiles(pipelineTarget);
997
+
998
+ return targetFiles
999
+ .filter(file => !currentFiles.has(normalizePipelineRelPath(file)))
1000
+ .filter(file => isLikelyManagedPipelineFile(file));
1001
+ }
1002
+
1003
+ export async function removeStaleManagedPipelineFiles(projectRoot, staleFiles, dryRun) {
1004
+ if (!staleFiles.length) return 0;
1005
+
1006
+ const pipelineTarget = path.join(projectRoot, '.prizmkit', 'dev-pipeline');
1007
+ const removedDirs = new Set();
1008
+ let removedCount = 0;
1009
+
1010
+ for (const relativeFile of staleFiles) {
1011
+ const targetFile = path.join(pipelineTarget, relativeFile);
1012
+ if (!await fs.pathExists(targetFile)) continue;
1013
+
1014
+ if (dryRun) {
1015
+ console.log(chalk.gray(` [dry-run] remove .prizmkit/dev-pipeline/${normalizePipelineRelPath(relativeFile)}`));
1016
+ } else {
1017
+ await fs.remove(targetFile);
1018
+ console.log(chalk.red(` ✗ removed .prizmkit/dev-pipeline/${normalizePipelineRelPath(relativeFile)}`));
1019
+ removedDirs.add(path.dirname(relativeFile));
1020
+ }
1021
+ removedCount++;
1022
+ }
1023
+
1024
+ if (!dryRun) {
1025
+ const dirsByDepth = [...removedDirs]
1026
+ .filter(dir => dir && dir !== '.')
1027
+ .sort((a, b) => b.split(path.sep).length - a.split(path.sep).length);
1028
+ for (const relativeDir of dirsByDepth) {
1029
+ const targetDir = path.join(pipelineTarget, relativeDir);
1030
+ if (!await fs.pathExists(targetDir)) continue;
1031
+ const entries = await fs.readdir(targetDir);
1032
+ if (entries.length === 0) {
1033
+ await fs.remove(targetDir);
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ return removedCount;
1039
+ }
1040
+
925
1041
  async function pruneStalePipelineRuntimeFiles(pipelineTarget, runtime) {
926
1042
  if (!await fs.pathExists(pipelineTarget)) return false;
927
1043
 
package/src/upgrade.js CHANGED
@@ -28,6 +28,8 @@ import {
28
28
  installGitignore,
29
29
  installProjectMemory,
30
30
  resolvePipelineFileList,
31
+ findStaleManagedPipelineFiles,
32
+ removeStaleManagedPipelineFiles,
31
33
  resolveRuleNamesForRuntime,
32
34
  resolveSkillList,
33
35
  EXTRAS_REGISTRY,
@@ -296,6 +298,11 @@ export async function runUpgrade(directory, options = {}) {
296
298
  }
297
299
 
298
300
  const diff = oldManifest ? diffManifest(oldManifest, newManifest) : { skills: { added: [], removed: [] }, agents: { added: [], removed: [] }, rules: { added: [], removed: [] }, pipeline: { added: [], removed: [] }, extras: { added: [], removed: [] } };
301
+ const removedPipelineSet = new Set(diff.pipeline.removed);
302
+ const staleManagedPipelineFiles = pipeline
303
+ ? (await findStaleManagedPipelineFiles(projectRoot, runtime))
304
+ .filter(file => !removedPipelineSet.has(file))
305
+ : [];
299
306
 
300
307
  // 5. Display upgrade summary
301
308
  const oldVersion = oldManifest?.version || 'unknown';
@@ -307,7 +314,7 @@ export async function runUpgrade(directory, options = {}) {
307
314
  console.log('');
308
315
 
309
316
  const totalAdded = diff.skills.added.length + diff.agents.added.length + diff.rules.added.length + diff.pipeline.added.length + diff.extras.added.length;
310
- const totalRemoved = diff.skills.removed.length + diff.agents.removed.length + diff.rules.removed.length + diff.pipeline.removed.length + diff.extras.removed.length;
317
+ const totalRemoved = diff.skills.removed.length + diff.agents.removed.length + diff.rules.removed.length + diff.pipeline.removed.length + diff.extras.removed.length + staleManagedPipelineFiles.length;
311
318
  const totalUpdated = newSkillList.length + newAgentFiles.length + newRuleFiles.length;
312
319
 
313
320
  if (diff.skills.added.length) console.log(chalk.green(` + Skills added: ${diff.skills.added.join(', ')}`));
@@ -318,6 +325,7 @@ export async function runUpgrade(directory, options = {}) {
318
325
  if (diff.rules.removed.length) console.log(chalk.red(` - Rules removed: ${diff.rules.removed.join(', ')}`));
319
326
  if (diff.pipeline.added.length) console.log(chalk.green(` + Pipeline files added: ${diff.pipeline.added.length} file(s)`));
320
327
  if (diff.pipeline.removed.length) console.log(chalk.red(` - Pipeline files removed: ${diff.pipeline.removed.length} file(s)`));
328
+ if (staleManagedPipelineFiles.length) console.log(chalk.red(` - Stale managed pipeline files: ${staleManagedPipelineFiles.length} file(s)`));
321
329
  if (diff.extras.added.length) console.log(chalk.green(` + Extras added: ${diff.extras.added.join(', ')}`));
322
330
  if (diff.extras.removed.length) console.log(chalk.red(` - Extras removed: ${diff.extras.removed.join(', ')}`));
323
331
 
@@ -344,7 +352,7 @@ export async function runUpgrade(directory, options = {}) {
344
352
  const platforms = expandPlatforms(platform);
345
353
 
346
354
  // 7a. Remove orphaned files
347
- if (diff.skills.removed.length || diff.agents.removed.length || diff.rules.removed.length || diff.pipeline.removed.length) {
355
+ if (diff.skills.removed.length || diff.agents.removed.length || diff.rules.removed.length || diff.pipeline.removed.length || staleManagedPipelineFiles.length) {
348
356
  console.log(chalk.bold('\n Removing orphaned files...'));
349
357
  for (const p of platforms) {
350
358
  if (diff.skills.removed.length) {
@@ -364,6 +372,10 @@ export async function runUpgrade(directory, options = {}) {
364
372
  console.log(chalk.blue('\n Removed pipeline files:'));
365
373
  await removePipelineFiles(projectRoot, diff.pipeline.removed, dryRun);
366
374
  }
375
+ if (staleManagedPipelineFiles.length) {
376
+ console.log(chalk.blue('\n Removed stale managed pipeline files:'));
377
+ await removeStaleManagedPipelineFiles(projectRoot, staleManagedPipelineFiles, dryRun);
378
+ }
367
379
  }
368
380
 
369
381
  // 7b. Re-install all current files (overwrite mode)