prizmkit 1.0.102 → 1.0.104

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.0.102",
3
- "bundledAt": "2026-03-24T16:16:55.231Z",
4
- "bundledFrom": "b3fb911"
2
+ "frameworkVersion": "1.0.104",
3
+ "bundledAt": "2026-03-25T14:43:36.061Z",
4
+ "bundledFrom": "a253848"
5
5
  }
@@ -1125,8 +1125,8 @@ case "${1:-run}" in
1125
1125
  run_one "$@"
1126
1126
  else
1127
1127
  # Parse positional and --features flag
1128
- local _run_feature_list="feature-list.json"
1129
- local _run_features_filter=""
1128
+ _run_feature_list="feature-list.json"
1129
+ _run_features_filter=""
1130
1130
  while [[ $# -gt 0 ]]; do
1131
1131
  case "$1" in
1132
1132
  --features)
@@ -8,6 +8,7 @@ to avoid duplication across pipeline scripts.
8
8
  import json
9
9
  import logging
10
10
  import os
11
+ import re
11
12
  import sys
12
13
 
13
14
 
@@ -107,26 +108,95 @@ def _build_progress_bar(percent, width=20):
107
108
  return "{} {:>3}%".format(bar, int(percent))
108
109
 
109
110
 
111
+ def _read_file_safe(filepath):
112
+ """Read a file and return its content, or empty string on error."""
113
+ try:
114
+ with open(filepath, "r", encoding="utf-8") as f:
115
+ return f.read()
116
+ except (IOError, OSError):
117
+ return ""
118
+
119
+
120
+ def _detect_node_runtime(project_root, pkg):
121
+ """Detect Node.js runtime version from engines, .nvmrc, or .node-version."""
122
+ engines = pkg.get("engines", {})
123
+ node_ver = engines.get("node", "")
124
+ if node_ver:
125
+ return "Node.js {}".format(node_ver)
126
+
127
+ for version_file in [".nvmrc", ".node-version"]:
128
+ vpath = os.path.join(project_root, version_file)
129
+ if os.path.isfile(vpath):
130
+ content = _read_file_safe(vpath).strip()
131
+ if content:
132
+ return "Node.js {}".format(content)
133
+
134
+ return "Node.js"
135
+
136
+
137
+ def _parse_python_deps(py_content, req_content):
138
+ """Extract Python package names from pyproject.toml and requirements.txt.
139
+
140
+ Returns a set of lowercased package names (without version specifiers).
141
+ """
142
+ deps = set()
143
+
144
+ # Parse requirements.txt lines: "package==1.0", "package>=2.0", "package"
145
+ for line in req_content.splitlines():
146
+ line = line.strip()
147
+ if not line or line.startswith("#") or line.startswith("-"):
148
+ continue
149
+ # Split on version specifiers and extras
150
+ name = re.split(r"[>=<!~;\[\s]", line, 1)[0].strip()
151
+ if name:
152
+ deps.add(name.lower())
153
+
154
+ # Parse pyproject.toml dependencies (simplified: look for quoted strings
155
+ # in [project.dependencies] and [project.optional-dependencies.*] sections)
156
+ in_deps_section = False
157
+ for line in py_content.splitlines():
158
+ stripped = line.strip()
159
+ if stripped.startswith("["):
160
+ in_deps_section = (
161
+ "dependencies" in stripped.lower()
162
+ and "optional" not in stripped.lower()
163
+ ) or "dependencies" in stripped.lower()
164
+ continue
165
+ if in_deps_section:
166
+ # Match quoted dependency: "flask>=2.0" or 'django~=4.0'
167
+ match = re.match(r"""^[\s"']*([a-zA-Z0-9_-]+)""", stripped)
168
+ if match:
169
+ deps.add(match.group(1).lower())
170
+
171
+ return deps
172
+
173
+
110
174
  def detect_project_context(project_root):
111
175
  """Auto-detect project tech stack from project files.
112
176
 
113
- Reads package.json, pyproject.toml, and common config files
114
- to infer language, test framework, and other stack details.
115
- Returns a dict of detected key-value pairs.
177
+ Reads package.json, pyproject.toml, requirements.txt, docker-compose,
178
+ and common config files to infer language, frameworks, styling,
179
+ database, ORM, bundler, testing, and project type.
180
+
181
+ Returns a dict of detected key-value pairs. Only detected fields are
182
+ included — no empty or null values. Adapts to any project type
183
+ (frontend-only, backend-only, fullstack, library, CLI, monorepo).
116
184
  """
117
185
  detected = {}
118
186
 
119
- # 1. Node.js / JavaScript / TypeScript project
187
+ # ── 1. Node.js / JavaScript / TypeScript project ──
120
188
  pkg_path = os.path.join(project_root, "package.json")
121
189
  if os.path.isfile(pkg_path):
122
190
  try:
123
191
  with open(pkg_path, "r", encoding="utf-8") as f:
124
192
  pkg = json.load(f)
125
193
 
126
- # Language detection
194
+ # All dependencies combined for detection
127
195
  deps = {}
128
196
  deps.update(pkg.get("dependencies", {}))
129
197
  deps.update(pkg.get("devDependencies", {}))
198
+
199
+ # Language
130
200
  if "typescript" in deps or os.path.isfile(
131
201
  os.path.join(project_root, "tsconfig.json")
132
202
  ):
@@ -134,7 +204,10 @@ def detect_project_context(project_root):
134
204
  else:
135
205
  detected["language"] = "JavaScript"
136
206
 
137
- # Test framework detection (order: more specific first)
207
+ # Runtime
208
+ detected["runtime"] = _detect_node_runtime(project_root, pkg)
209
+
210
+ # Test framework (more specific first)
138
211
  scripts = pkg.get("scripts", {})
139
212
  test_script = (
140
213
  scripts.get("test", "")
@@ -150,35 +223,256 @@ def detect_project_context(project_root):
150
223
  elif "--test" in test_script or "node:test" in test_script:
151
224
  detected["testing_framework"] = "Node.js built-in test runner"
152
225
 
153
- # Framework detection
154
- if "next" in deps:
155
- detected["framework"] = "Next.js"
156
- elif "express" in deps:
157
- detected["framework"] = "Express.js"
158
- elif "fastify" in deps:
159
- detected["framework"] = "Fastify"
160
- elif "react" in deps:
161
- detected["framework"] = "React"
162
- elif "vue" in deps:
163
- detected["framework"] = "Vue.js"
226
+ # ── Frontend framework ──
227
+ frontend_frameworks = [
228
+ ("next", "Next.js"),
229
+ ("nuxt", "Nuxt"),
230
+ ("@angular/core", "Angular"),
231
+ ("svelte", "Svelte"),
232
+ ("solid-js", "Solid.js"),
233
+ ("react", "React"),
234
+ ("vue", "Vue.js"),
235
+ ]
236
+ for dep_name, fw_name in frontend_frameworks:
237
+ if dep_name in deps:
238
+ detected["frontend_framework"] = fw_name
239
+ break
240
+
241
+ # ── Backend framework ──
242
+ backend_frameworks = [
243
+ ("@nestjs/core", "NestJS"),
244
+ ("express", "Express.js"),
245
+ ("fastify", "Fastify"),
246
+ ("koa", "Koa"),
247
+ ("hapi", "Hapi"),
248
+ ("hono", "Hono"),
249
+ ]
250
+ for dep_name, fw_name in backend_frameworks:
251
+ if dep_name in deps:
252
+ detected["backend_framework"] = fw_name
253
+ break
254
+
255
+ # Legacy "framework" field for backward compatibility
256
+ if "frontend_framework" in detected:
257
+ detected["framework"] = detected["frontend_framework"]
258
+ elif "backend_framework" in detected:
259
+ detected["framework"] = detected["backend_framework"]
260
+
261
+ # ── Frontend styling ──
262
+ styling_libs = [
263
+ ("tailwindcss", "Tailwind CSS"),
264
+ ("@tailwindcss/vite", "Tailwind CSS"),
265
+ ("styled-components", "Styled Components"),
266
+ ("@emotion/react", "Emotion"),
267
+ ("@emotion/styled", "Emotion"),
268
+ ("@mui/material", "Material UI"),
269
+ ("@chakra-ui/react", "Chakra UI"),
270
+ ("antd", "Ant Design"),
271
+ ("sass", "SCSS/Sass"),
272
+ ("node-sass", "SCSS/Sass"),
273
+ ("less", "Less"),
274
+ ]
275
+ for dep_name, style_name in styling_libs:
276
+ if dep_name in deps:
277
+ detected["frontend_styling"] = style_name
278
+ break
279
+
280
+ # ── Database ──
281
+ db_libs = [
282
+ ("pg", "PostgreSQL"),
283
+ ("postgres", "PostgreSQL"),
284
+ ("mysql2", "MySQL"),
285
+ ("mysql", "MySQL"),
286
+ ("better-sqlite3", "SQLite"),
287
+ ("mongodb", "MongoDB"),
288
+ ("mongoose", "MongoDB"),
289
+ ("redis", "Redis"),
290
+ ("ioredis", "Redis"),
291
+ ]
292
+ for dep_name, db_name in db_libs:
293
+ if dep_name in deps:
294
+ detected["database"] = db_name
295
+ break
296
+
297
+ # ── ORM ──
298
+ orm_libs = [
299
+ ("@prisma/client", "Prisma"),
300
+ ("prisma", "Prisma"),
301
+ ("drizzle-orm", "Drizzle"),
302
+ ("typeorm", "TypeORM"),
303
+ ("sequelize", "Sequelize"),
304
+ ("mongoose", "Mongoose"),
305
+ ("knex", "Knex.js"),
306
+ ("@mikro-orm/core", "MikroORM"),
307
+ ]
308
+ for dep_name, orm_name in orm_libs:
309
+ if dep_name in deps:
310
+ detected["orm"] = orm_name
311
+ break
312
+
313
+ # ── Bundler ──
314
+ bundler_libs = [
315
+ ("vite", "Vite"),
316
+ ("webpack", "Webpack"),
317
+ ("esbuild", "esbuild"),
318
+ ("rollup", "Rollup"),
319
+ ("parcel", "Parcel"),
320
+ ("turbo", "Turborepo"),
321
+ ("@rspack/core", "Rspack"),
322
+ ]
323
+ for dep_name, bundler_name in bundler_libs:
324
+ if dep_name in deps:
325
+ detected["bundler"] = bundler_name
326
+ break
327
+
328
+ # ── Project type inference ──
329
+ has_frontend = "frontend_framework" in detected
330
+ has_backend = "backend_framework" in detected
331
+ has_workspaces = "workspaces" in pkg
332
+ has_bin = "bin" in pkg
333
+
334
+ if has_workspaces:
335
+ detected["project_type"] = "monorepo"
336
+ elif has_frontend and has_backend:
337
+ detected["project_type"] = "fullstack"
338
+ elif has_frontend:
339
+ detected["project_type"] = "frontend"
340
+ elif has_backend:
341
+ detected["project_type"] = "backend"
342
+ elif has_bin:
343
+ detected["project_type"] = "cli"
344
+ elif "main" in pkg or "exports" in pkg:
345
+ detected["project_type"] = "library"
346
+
164
347
  except (json.JSONDecodeError, IOError):
165
348
  pass
166
349
 
167
- # 2. Python project detection
168
- if not detected:
350
+ # ── 2. Python project detection ──
351
+ if "language" not in detected:
352
+ py_content = ""
169
353
  for marker in ["pyproject.toml", "setup.py", "requirements.txt"]:
170
- if os.path.isfile(os.path.join(project_root, marker)):
354
+ marker_path = os.path.join(project_root, marker)
355
+ if os.path.isfile(marker_path):
171
356
  detected["language"] = "Python"
172
- # Check for pytest
173
- toml_path = os.path.join(project_root, "pyproject.toml")
174
- if os.path.isfile(toml_path):
175
- try:
176
- with open(toml_path, "r", encoding="utf-8") as f:
177
- content = f.read()
178
- if "pytest" in content:
179
- detected["testing_framework"] = "pytest"
180
- except IOError:
181
- pass
357
+ if marker == "pyproject.toml":
358
+ py_content = _read_file_safe(marker_path)
359
+ break
360
+
361
+ if detected.get("language") == "Python":
362
+ req_path = os.path.join(project_root, "requirements.txt")
363
+ req_content = _read_file_safe(req_path) if os.path.isfile(req_path) else ""
364
+ py_deps = _parse_python_deps(py_content, req_content)
365
+
366
+ # Runtime version (regex for requires-python = ">=3.11")
367
+ if py_content:
368
+ ver_match = re.search(
369
+ r'requires-python\s*=\s*["\']([^"\']+)["\']', py_content
370
+ )
371
+ if ver_match:
372
+ detected["runtime"] = "Python {}".format(ver_match.group(1))
373
+
374
+ # Testing
375
+ if "pytest" in py_deps:
376
+ detected["testing_framework"] = "pytest"
377
+
378
+ # Backend framework
379
+ py_backend = [
380
+ ("django", "Django"),
381
+ ("fastapi", "FastAPI"),
382
+ ("flask", "Flask"),
383
+ ("starlette", "Starlette"),
384
+ ("tornado", "Tornado"),
385
+ ("aiohttp", "aiohttp"),
386
+ ]
387
+ for dep_name, fw_name in py_backend:
388
+ if dep_name in py_deps:
389
+ detected["backend_framework"] = fw_name
390
+ detected["framework"] = fw_name
391
+ break
392
+
393
+ # Database / ORM
394
+ py_db = [
395
+ ("psycopg2", "PostgreSQL"),
396
+ ("psycopg", "PostgreSQL"),
397
+ ("asyncpg", "PostgreSQL"),
398
+ ("pymysql", "MySQL"),
399
+ ("pymongo", "MongoDB"),
400
+ ("motor", "MongoDB"),
401
+ ]
402
+ for dep_name, db_name in py_db:
403
+ if dep_name in py_deps:
404
+ detected["database"] = db_name
405
+ break
406
+
407
+ py_orm = [
408
+ ("sqlalchemy", "SQLAlchemy"),
409
+ ("tortoise-orm", "Tortoise ORM"),
410
+ ("peewee", "Peewee"),
411
+ ]
412
+ for dep_name, orm_name in py_orm:
413
+ if dep_name in py_deps:
414
+ detected["orm"] = orm_name
415
+ break
416
+ # Django ORM: if django is a dep and no other ORM detected
417
+ if "django" in py_deps and "orm" not in detected:
418
+ detected["orm"] = "Django ORM"
419
+
420
+ # Project type
421
+ if "backend_framework" in detected:
422
+ detected["project_type"] = "backend"
423
+
424
+ # ── 3. Go project detection ──
425
+ if "language" not in detected:
426
+ go_mod_path = os.path.join(project_root, "go.mod")
427
+ if os.path.isfile(go_mod_path):
428
+ detected["language"] = "Go"
429
+ detected["runtime"] = "Go"
430
+ go_content = _read_file_safe(go_mod_path)
431
+ if "gin-gonic" in go_content:
432
+ detected["backend_framework"] = "Gin"
433
+ elif "labstack/echo" in go_content:
434
+ detected["backend_framework"] = "Echo"
435
+ elif "go-chi/chi" in go_content:
436
+ detected["backend_framework"] = "Chi"
437
+ if "backend_framework" in detected:
438
+ detected["framework"] = detected["backend_framework"]
439
+ detected["project_type"] = "backend"
440
+
441
+ # ── 4. Rust / Java / other languages (basic detection) ──
442
+ if "language" not in detected:
443
+ if os.path.isfile(os.path.join(project_root, "Cargo.toml")):
444
+ detected["language"] = "Rust"
445
+ detected["runtime"] = "Rust"
446
+ elif os.path.isfile(os.path.join(project_root, "pom.xml")):
447
+ detected["language"] = "Java"
448
+ detected["runtime"] = "Java (Maven)"
449
+ elif os.path.isfile(os.path.join(project_root, "build.gradle")):
450
+ detected["language"] = "Java/Kotlin"
451
+ detected["runtime"] = "Java (Gradle)"
452
+
453
+ # ── 5. Database from docker-compose (cross-language) ──
454
+ if "database" not in detected:
455
+ for dc_name in [
456
+ "docker-compose.yml",
457
+ "docker-compose.yaml",
458
+ "compose.yml",
459
+ "compose.yaml",
460
+ ]:
461
+ dc_path = os.path.join(project_root, dc_name)
462
+ if os.path.isfile(dc_path):
463
+ dc_content = _read_file_safe(dc_path).lower()
464
+ dc_db = [
465
+ ("postgres", "PostgreSQL"),
466
+ ("mysql", "MySQL"),
467
+ ("mariadb", "MariaDB"),
468
+ ("mongo", "MongoDB"),
469
+ ("redis", "Redis"),
470
+ ("sqlite", "SQLite"),
471
+ ]
472
+ for pattern, db_name in dc_db:
473
+ if pattern in dc_content:
474
+ detected["database"] = db_name
475
+ break
182
476
  break
183
477
 
184
478
  return detected
@@ -199,10 +493,18 @@ def enrich_global_context(global_context, project_root):
199
493
  "language": "language",
200
494
  "testing_framework": "testing_strategy",
201
495
  "framework": "framework",
496
+ "frontend_framework": "frontend_framework",
497
+ "frontend_styling": "frontend_styling",
498
+ "backend_framework": "backend_framework",
499
+ "database": "database",
500
+ "orm": "orm",
501
+ "bundler": "bundler",
502
+ "project_type": "project_type",
503
+ "runtime": "runtime",
202
504
  }
203
505
  # Alternate key names that should block auto-detection
204
506
  alt_keys = {
205
- "testing_strategy": ["testing_framework", "test_framework"],
507
+ "testing_strategy": ["testing_framework", "test_framework", "testing"],
206
508
  }
207
509
  for det_key, ctx_key in key_mapping.items():
208
510
  if det_key not in detected:
@@ -151,7 +151,16 @@
151
151
  "description": "Global context for all bug fixes in this batch",
152
152
  "properties": {
153
153
  "tech_stack": { "type": "string" },
154
- "testing_framework": { "type": "string" },
154
+ "language": { "type": "string" },
155
+ "runtime": { "type": "string" },
156
+ "frontend_framework": { "type": "string" },
157
+ "frontend_styling": { "type": "string" },
158
+ "backend_framework": { "type": "string" },
159
+ "database": { "type": "string" },
160
+ "orm": { "type": "string" },
161
+ "testing_strategy": { "type": "string" },
162
+ "bundler": { "type": "string" },
163
+ "project_type": { "type": "string" },
155
164
  "ci_pipeline": { "type": "string" }
156
165
  }
157
166
  }
@@ -108,8 +108,17 @@
108
108
  "type": "object",
109
109
  "properties": {
110
110
  "tech_stack": { "type": "string" },
111
+ "language": { "type": "string" },
112
+ "runtime": { "type": "string" },
113
+ "frontend_framework": { "type": "string" },
114
+ "frontend_styling": { "type": "string" },
115
+ "backend_framework": { "type": "string" },
116
+ "database": { "type": "string" },
117
+ "orm": { "type": "string" },
111
118
  "design_system": { "type": "string" },
112
- "testing_strategy": { "type": "string" }
119
+ "testing_strategy": { "type": "string" },
120
+ "bundler": { "type": "string" },
121
+ "project_type": { "type": "string" }
113
122
  }
114
123
  }
115
124
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.102",
2
+ "version": "1.0.104",
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.",
@@ -47,8 +47,15 @@ Do NOT use this skill when:
47
47
 
48
48
  Before questions, check optional context files (never block if absent):
49
49
  - `.prizm-docs/root.prizm` (architecture/project context)
50
- - `.prizmkit/config.json` (existing stack preferences)
50
+ - `.prizmkit/config.json` (existing stack preferences and detected tech stack)
51
51
  - existing `feature-list.json` (required for incremental mode)
52
+
53
+ **Tech stack auto-population from config.json:**
54
+ - If `.prizmkit/config.json` contains a `tech_stack` object, use it to pre-fill `global_context` fields in the generated `feature-list.json`.
55
+ - Map config fields to global_context: `language`, `runtime`, `frontend_framework`, `frontend_styling`, `backend_framework`, `database`, `orm`, `testing` → `testing_strategy`, `bundler`, `project_type`.
56
+ - Do NOT re-ask the user for tech stack info that is already present in config.json. Instead, show the detected stack and ask: "Is this correct? Any changes?"
57
+ - If config.json has no `tech_stack`, fall back to asking during Phase 2 (constraints and tech assumptions).
58
+
52
59
  Note:
53
60
  - This skill **reads** `.prizmkit/config.json` if present.
54
61
  - This skill does **not** create `.prizmkit/config.json` directly.
@@ -43,8 +43,8 @@ Launch the interactive bug planning process through 4 phases.
43
43
  ### Phase 1: Project Context
44
44
 
45
45
  1. **Identify project**: Read project name and description from existing `feature-list.json` or ask user
46
- 2. **Identify tech stack**: Read from `feature-list.json` global_context or `.prizm-docs/root.prizm`, or ask user
47
- 3. **Identify testing framework**: Auto-detect from package.json/requirements.txt/etc., or ask user
46
+ 2. **Identify tech stack**: Read from `.prizmkit/config.json` `tech_stack` (preferred), then `feature-list.json` global_context, then `.prizm-docs/root.prizm`. Only ask user if none of these sources provide tech stack info.
47
+ 3. **Identify testing framework**: Read from `.prizmkit/config.json` `tech_stack.testing`, or auto-detect from package.json/requirements.txt/etc., or ask user
48
48
 
49
49
  Output: `project_name`, `project_description`, `global_context` fields populated.
50
50
 
@@ -35,6 +35,8 @@ Project takeover and bootstrap skill. Scans any project (brownfield or greenfiel
35
35
 
36
36
  MODE DETECTION:
37
37
  - If `.prizm-docs/` exists: Ask user if they want to reinitialize or update
38
+ - **Reinitialize**: overwrites `.prizm-docs/` and `config.json` tech_stack (fresh start)
39
+ - **Update**: re-scans tech stack and merges changes into existing `config.json` (see Step 3b merge strategy); updates `root.prizm` TECH_STACK if changed; preserves existing `.prizm-docs/` L1/L2 docs
38
40
  - If project has source code: brownfield mode
39
41
  - If project is nearly empty: greenfield mode
40
42
 
@@ -50,6 +52,18 @@ BROWNFIELD WORKFLOW (existing project):
50
52
  3. Identify entry points by language convention
51
53
  4. Catalog dependencies (external packages)
52
54
  5. Count source files per directory
55
+ 6. Detect detailed tech stack (adaptive — only include fields that apply to this project):
56
+ - **Language & Runtime**: e.g. TypeScript + Node.js 20, Python 3.11, Go 1.22
57
+ - **Frontend framework** (if applicable): React, Vue.js, Angular, Next.js, Svelte, etc.
58
+ - **Frontend styling** (if applicable): Tailwind CSS, SCSS, Styled Components, Material UI, etc.
59
+ - **Backend framework** (if applicable): Express.js, FastAPI, Django, NestJS, Gin, etc.
60
+ - **Database** (if applicable): PostgreSQL, MySQL, MongoDB, Redis, SQLite — detected from deps or `docker-compose.yml`
61
+ - **ORM** (if applicable): Prisma, Drizzle, TypeORM, SQLAlchemy, Mongoose, etc.
62
+ - **Bundler** (if applicable): Vite, Webpack, esbuild, Rollup, Turborepo
63
+ - **Testing**: Vitest, Jest, pytest, Go test, etc.
64
+ - **Project type** (inferred): `frontend` | `backend` | `fullstack` | `library` | `cli` | `monorepo`
65
+
66
+ **IMPORTANT**: Not all projects have all fields. A pure backend API will have no `frontend_framework` or `frontend_styling`. A library may have no database. Only record what is actually detected — never generate empty or placeholder values.
53
67
 
54
68
  **Step 2: Prizm Documentation Generation**
55
69
  Invoke prizmkit-prizm-docs (Init operation), passing the two-tier module structure from Step 1:
@@ -64,6 +78,57 @@ Invoke prizmkit-prizm-docs (Init operation), passing the two-tier module structu
64
78
  - `.prizmkit/config.json` (adoption_mode, speckit_hooks_enabled, platform)
65
79
  - `.prizmkit/specs/` (empty)
66
80
 
81
+ 3b. Write detected tech stack to `.prizmkit/config.json`:
82
+
83
+ **Merge strategy** (handles re-init without losing user edits):
84
+ - Read existing `config.json` if present
85
+ - If `tech_stack` field exists AND `_auto_detected` is `false` or absent:
86
+ → **SKIP** — user has manually configured tech stack, preserve their settings
87
+ - If `tech_stack` field exists AND `_auto_detected` is `true`:
88
+ → **MERGE** — overwrite auto-detected values with new detection results, but preserve any keys the user added manually (keys not in the new detection result)
89
+ - If `tech_stack` field does NOT exist:
90
+ → **WRITE** full detected tech stack with `"_auto_detected": true`
91
+ - Only include fields that were actually detected (no empty/null values)
92
+
93
+ Example config.json after init (fullstack project):
94
+ ```json
95
+ {
96
+ "adoption_mode": "passive",
97
+ "platform": "claude",
98
+ "tech_stack": {
99
+ "language": "TypeScript",
100
+ "runtime": "Node.js 20",
101
+ "frontend_framework": "React",
102
+ "frontend_styling": "Tailwind CSS",
103
+ "backend_framework": "Express.js",
104
+ "database": "PostgreSQL",
105
+ "orm": "Prisma",
106
+ "testing": "Vitest",
107
+ "bundler": "Vite",
108
+ "project_type": "fullstack",
109
+ "_auto_detected": true
110
+ }
111
+ }
112
+ ```
113
+
114
+ Example config.json after init (pure Python backend):
115
+ ```json
116
+ {
117
+ "adoption_mode": "passive",
118
+ "platform": "claude",
119
+ "tech_stack": {
120
+ "language": "Python",
121
+ "runtime": "Python >=3.11",
122
+ "backend_framework": "FastAPI",
123
+ "database": "PostgreSQL",
124
+ "orm": "SQLAlchemy",
125
+ "testing": "pytest",
126
+ "project_type": "backend",
127
+ "_auto_detected": true
128
+ }
129
+ }
130
+ ```
131
+
67
132
  **Step 4: Hook & Settings Configuration**
68
133
 
69
134
  4a. Read or create platform settings file (`.codebuddy/settings.json` or `.claude/settings.json`)
@@ -74,15 +139,44 @@ Invoke prizmkit-prizm-docs (Init operation), passing the two-tier module structu
74
139
  4d. Preserve any existing hooks and settings — never overwrite user's custom configuration
75
140
 
76
141
  **Step 5: Report**
77
- Output summary: platform detected, tech stack detected, modules discovered, L1 docs generated, platform-specific configuration applied, next recommended steps.
142
+ Output summary: platform detected, tech stack detected (with detail), modules discovered, L1 docs generated, platform-specific configuration applied, next recommended steps.
143
+
144
+ Tech stack report format (only show detected fields, adapt to project type):
145
+ ```
146
+ Tech stack detected:
147
+ Language: TypeScript
148
+ Runtime: Node.js 20
149
+ Frontend: React + Tailwind CSS
150
+ Backend: Express.js
151
+ Database: PostgreSQL (Prisma)
152
+ Testing: Vitest
153
+ Bundler: Vite
154
+ Project type: fullstack
155
+ ```
156
+
157
+ For a pure backend Python project, the report would show:
158
+ ```
159
+ Tech stack detected:
160
+ Language: Python
161
+ Runtime: Python >=3.11
162
+ Backend: FastAPI
163
+ Database: PostgreSQL (SQLAlchemy)
164
+ Testing: pytest
165
+ Project type: backend
166
+ ```
167
+
168
+ Saved to: `.prizmkit/config.json` → `tech_stack` field
78
169
 
79
170
  Include platform-specific guidance:
80
171
  - CodeBuddy: "Use `/prizmkit-specify` to start your first feature"
81
172
  - Claude Code: "Use `/prizmkit-specify` to start your first feature"
82
173
 
83
174
  GREENFIELD WORKFLOW (new project):
84
- - Skip Step 1 (no code to scan)
85
- - Step 2: Create minimal `.prizm-docs/` with just `root.prizm` skeleton
175
+ - Skip Step 1 (no code to scan) — but ask the user about their intended tech stack:
176
+ - "What language/framework will you use?" (e.g. React + Node.js, Python + FastAPI, etc.)
177
+ - Record answers in `config.json` `tech_stack` with `"_auto_detected": false` (user-provided, not auto-detected)
178
+ - If user is unsure, skip tech_stack — it can be populated later on re-init after code exists
179
+ - Step 2: Create minimal `.prizm-docs/` with just `root.prizm` skeleton (populate TECH_STACK from user answers if provided)
86
180
  - Steps 3-5: Same as brownfield (Step 5 Report recommends starting with specify for first feature)
87
181
 
88
182
  ### Gradual Adoption Path
@@ -107,14 +201,23 @@ User can change mode in `.prizmkit/config.json`: `"adoption_mode": "passive" | "
107
201
 
108
202
  ## Example
109
203
 
110
- **Brownfield init on a Node.js project:**
204
+ **Brownfield init on a fullstack Node.js project:**
111
205
  ```
112
206
  $ /prizmkit-init
113
207
 
114
208
  Platform detected: Claude Code
115
- Tech stack: TypeScript, Node.js, Express
116
209
  Mode: Brownfield (154 source files found)
117
210
 
211
+ Tech stack detected:
212
+ Language: TypeScript
213
+ Runtime: Node.js 20
214
+ Frontend: React + Tailwind CSS
215
+ Backend: Express.js
216
+ Database: PostgreSQL (Prisma)
217
+ Testing: Vitest
218
+ Bundler: Vite
219
+ Project type: fullstack
220
+
118
221
  Modules discovered:
119
222
  src/routes/ → .prizm-docs/routes.prizm (12 files)
120
223
  src/models/ → .prizm-docs/models.prizm (8 files)
@@ -123,8 +226,25 @@ Modules discovered:
123
226
 
124
227
  Generated: root.prizm + 4 L1 docs + changelog.prizm
125
228
  Configured: .claude/rules/ (2 files), hooks in settings.json
229
+ Saved: .prizmkit/config.json (tech_stack recorded)
126
230
 
127
231
  Next: Use /prizmkit-specify to start your first feature
128
232
  ```
129
233
 
234
+ **Re-init after PrizmKit upgrade (existing config preserved):**
235
+ ```
236
+ $ /prizmkit-init
237
+
238
+ .prizm-docs/ already exists. Reinitialize or update?
239
+ > Update (preserve existing docs)
240
+
241
+ Tech stack changes detected:
242
+ + bundler: Vite (newly detected)
243
+ ~ testing: Jest → Vitest (updated)
244
+ = language: TypeScript (unchanged)
245
+ = frontend: React (unchanged)
246
+
247
+ Merged into .prizmkit/config.json (2 fields updated, user overrides preserved)
248
+ ```
249
+
130
250
  IMPORTANT: Use `${SKILL_DIR}` placeholder for all path references. Never hardcode absolute paths.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prizmkit",
3
- "version": "1.0.102",
3
+ "version": "1.0.104",
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": {