wize-dev-kit 0.3.0 → 0.3.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/AGENTS.md +21 -0
- package/ARCH.md +40 -4
- package/CHANGELOG.md +26 -0
- package/README.md +3 -1
- package/package.json +3 -2
- package/src/method-skills/1-analysis/wize-document-project/documentation-requirements.csv +12 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/api-contracts-template.md +38 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/architecture-template.md +54 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/component-inventory-template.md +40 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/contribution-guide-template.md +34 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/data-models-template.md +36 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/deep-dive-template.md +312 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/deployment-guide-template.md +42 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/development-guide-template.md +61 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/index-template.md +185 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/project-overview-template.md +110 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/project-scan-report-schema.json +159 -0
- package/src/method-skills/1-analysis/wize-document-project/templates/source-tree-template.md +142 -0
- package/src/method-skills/1-analysis/wize-document-project/workflow.md +23 -0
- package/src/tea-skills/wize-tea-risk/workflow.md +11 -0
- package/tools/installer/commands/doctor.js +27 -1
- package/tools/installer/commands/document-project.js +93 -0
- package/tools/installer/document-project/batch-scanner.js +93 -0
- package/tools/installer/document-project/classify.js +170 -0
- package/tools/installer/document-project/modes/deep-dive.js +196 -0
- package/tools/installer/document-project/modes/full-rescan.js +15 -0
- package/tools/installer/document-project/modes/initial-scan.js +100 -0
- package/tools/installer/document-project/modes/quick.js +211 -0
- package/tools/installer/document-project/render-index.js +101 -0
- package/tools/installer/document-project/state.js +110 -0
- package/tools/installer/wize-cli.js +46 -30
package/src/method-skills/1-analysis/wize-document-project/templates/project-scan-report-schema.json
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://qwize.io/wize-dev-kit/schemas/project-scan-report.schema.json",
|
|
4
|
+
"title": "Project Scan Report",
|
|
5
|
+
"description": "State tracking file for wize-document-project resumability",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["workflow_version", "timestamps", "mode", "scan_level", "completed_steps", "current_step"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"workflow_version": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Version of document-project workflow"
|
|
12
|
+
},
|
|
13
|
+
"timestamps": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["started", "last_updated"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"started": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"format": "date-time",
|
|
20
|
+
"description": "ISO 8601 timestamp when workflow started"
|
|
21
|
+
},
|
|
22
|
+
"last_updated": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"format": "date-time",
|
|
25
|
+
"description": "ISO 8601 timestamp of last state update"
|
|
26
|
+
},
|
|
27
|
+
"completed": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"format": "date-time",
|
|
30
|
+
"description": "ISO 8601 timestamp when workflow completed"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"mode": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"enum": ["initial_scan", "full_rescan", "deep_dive", "quick"],
|
|
37
|
+
"description": "Workflow execution mode"
|
|
38
|
+
},
|
|
39
|
+
"scan_level": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": ["quick", "deep", "exhaustive"],
|
|
42
|
+
"description": "Scan depth level"
|
|
43
|
+
},
|
|
44
|
+
"project_root": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Absolute path to project root directory"
|
|
47
|
+
},
|
|
48
|
+
"project_knowledge": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Absolute path to project knowledge folder"
|
|
51
|
+
},
|
|
52
|
+
"completed_steps": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"required": ["step", "status"],
|
|
57
|
+
"properties": {
|
|
58
|
+
"step": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Step identifier"
|
|
61
|
+
},
|
|
62
|
+
"status": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": ["completed", "partial", "failed"]
|
|
65
|
+
},
|
|
66
|
+
"timestamp": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"format": "date-time"
|
|
69
|
+
},
|
|
70
|
+
"outputs": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": { "type": "string" },
|
|
73
|
+
"description": "Files written during this step"
|
|
74
|
+
},
|
|
75
|
+
"summary": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "1-2 sentence summary of step outcome"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"current_step": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Current step identifier for resumption"
|
|
85
|
+
},
|
|
86
|
+
"findings": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"description": "High-level summaries only",
|
|
89
|
+
"properties": {
|
|
90
|
+
"project_classification": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"properties": {
|
|
93
|
+
"repository_type": { "type": "string" },
|
|
94
|
+
"parts_count": { "type": "integer" },
|
|
95
|
+
"primary_language": { "type": "string" },
|
|
96
|
+
"architecture_type": { "type": "string" }
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"technology_stack": {
|
|
100
|
+
"type": "array",
|
|
101
|
+
"items": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"properties": {
|
|
104
|
+
"part_id": { "type": "string" },
|
|
105
|
+
"tech_summary": { "type": "string" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"batches_completed": {
|
|
110
|
+
"type": "array",
|
|
111
|
+
"items": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"properties": {
|
|
114
|
+
"path": { "type": "string" },
|
|
115
|
+
"files_scanned": { "type": "integer" },
|
|
116
|
+
"summary": { "type": "string" }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"outputs_generated": {
|
|
123
|
+
"type": "array",
|
|
124
|
+
"items": { "type": "string" },
|
|
125
|
+
"description": "List of all output files generated"
|
|
126
|
+
},
|
|
127
|
+
"resume_instructions": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"description": "Instructions for resuming from current_step"
|
|
130
|
+
},
|
|
131
|
+
"validation_status": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"properties": {
|
|
134
|
+
"last_validated": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"format": "date-time"
|
|
137
|
+
},
|
|
138
|
+
"validation_errors": {
|
|
139
|
+
"type": "array",
|
|
140
|
+
"items": { "type": "string" }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"deep_dive_targets": {
|
|
145
|
+
"type": "array",
|
|
146
|
+
"description": "Track deep-dive areas analyzed",
|
|
147
|
+
"items": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"properties": {
|
|
150
|
+
"target_name": { "type": "string" },
|
|
151
|
+
"target_path": { "type": "string" },
|
|
152
|
+
"files_analyzed": { "type": "integer" },
|
|
153
|
+
"output_file": { "type": "string" },
|
|
154
|
+
"timestamp": { "type": "string", "format": "date-time" }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: baseline
|
|
3
|
+
owner: Pepper Potts + Peggy Carter
|
|
4
|
+
created: {{date}}
|
|
5
|
+
last_refreshed: {{date}}
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# {{project_name}} - Source Tree Analysis
|
|
9
|
+
|
|
10
|
+
**Date:** {{date}}
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
{{source_tree_overview}}
|
|
15
|
+
|
|
16
|
+
{{#if is_multi_part}}
|
|
17
|
+
|
|
18
|
+
## Multi-Part Structure
|
|
19
|
+
|
|
20
|
+
This project is organized into {{parts_count}} distinct parts:
|
|
21
|
+
|
|
22
|
+
{{#each project_parts}}
|
|
23
|
+
|
|
24
|
+
- **{{part_name}}** (`{{root_path}}`): {{purpose}}
|
|
25
|
+
{{/each}}
|
|
26
|
+
{{/if}}
|
|
27
|
+
|
|
28
|
+
## Complete Directory Structure
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
{{complete_source_tree}}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Critical Directories
|
|
35
|
+
|
|
36
|
+
{{#each critical_folders}}
|
|
37
|
+
|
|
38
|
+
### `{{folder_path}}`
|
|
39
|
+
|
|
40
|
+
{{description}}
|
|
41
|
+
|
|
42
|
+
**Purpose:** {{purpose}}
|
|
43
|
+
**Contains:** {{contents_summary}}
|
|
44
|
+
{{#if entry_points}}**Entry Points:** {{entry_points}}{{/if}}
|
|
45
|
+
{{#if integration_note}}**Integration:** {{integration_note}}{{/if}}
|
|
46
|
+
|
|
47
|
+
{{/each}}
|
|
48
|
+
|
|
49
|
+
{{#if is_multi_part}}
|
|
50
|
+
|
|
51
|
+
## Part-Specific Trees
|
|
52
|
+
|
|
53
|
+
{{#each project_parts}}
|
|
54
|
+
|
|
55
|
+
### {{part_name}} Structure
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
{{source_tree}}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Key Directories:**
|
|
62
|
+
{{#each critical_directories}}
|
|
63
|
+
|
|
64
|
+
- **`{{path}}`**: {{description}}
|
|
65
|
+
{{/each}}
|
|
66
|
+
|
|
67
|
+
{{/each}}
|
|
68
|
+
|
|
69
|
+
## Integration Points
|
|
70
|
+
|
|
71
|
+
{{#each integration_points}}
|
|
72
|
+
|
|
73
|
+
### {{from_part}} → {{to_part}}
|
|
74
|
+
|
|
75
|
+
- **Location:** `{{integration_path}}`
|
|
76
|
+
- **Type:** {{integration_type}}
|
|
77
|
+
- **Details:** {{details}}
|
|
78
|
+
{{/each}}
|
|
79
|
+
|
|
80
|
+
{{/if}}
|
|
81
|
+
|
|
82
|
+
## Entry Points
|
|
83
|
+
|
|
84
|
+
{{#if is_single_part}}
|
|
85
|
+
|
|
86
|
+
- **Main Entry:** `{{main_entry_point}}`
|
|
87
|
+
{{#if additional_entry_points}}
|
|
88
|
+
- **Additional:**
|
|
89
|
+
{{#each additional_entry_points}}
|
|
90
|
+
- `{{path}}`: {{description}}
|
|
91
|
+
{{/each}}
|
|
92
|
+
{{/if}}
|
|
93
|
+
{{else}}
|
|
94
|
+
{{#each project_parts}}
|
|
95
|
+
|
|
96
|
+
### {{part_name}}
|
|
97
|
+
|
|
98
|
+
- **Entry Point:** `{{entry_point}}`
|
|
99
|
+
- **Bootstrap:** {{bootstrap_description}}
|
|
100
|
+
{{/each}}
|
|
101
|
+
{{/if}}
|
|
102
|
+
|
|
103
|
+
## File Organization Patterns
|
|
104
|
+
|
|
105
|
+
{{file_organization_patterns}}
|
|
106
|
+
|
|
107
|
+
## Key File Types
|
|
108
|
+
|
|
109
|
+
{{#each file_type_patterns}}
|
|
110
|
+
|
|
111
|
+
### {{file_type}}
|
|
112
|
+
|
|
113
|
+
- **Pattern:** `{{pattern}}`
|
|
114
|
+
- **Purpose:** {{purpose}}
|
|
115
|
+
- **Examples:** {{examples}}
|
|
116
|
+
{{/each}}
|
|
117
|
+
|
|
118
|
+
## Asset Locations
|
|
119
|
+
|
|
120
|
+
{{#if has_assets}}
|
|
121
|
+
{{#each asset_locations}}
|
|
122
|
+
|
|
123
|
+
- **{{asset_type}}**: `{{location}}` ({{file_count}} files, {{total_size}})
|
|
124
|
+
{{/each}}
|
|
125
|
+
{{else}}
|
|
126
|
+
No significant assets detected.
|
|
127
|
+
{{/if}}
|
|
128
|
+
|
|
129
|
+
## Configuration Files
|
|
130
|
+
|
|
131
|
+
{{#each config_files}}
|
|
132
|
+
|
|
133
|
+
- **`{{path}}`**: {{description}}
|
|
134
|
+
{{/each}}
|
|
135
|
+
|
|
136
|
+
## Notes for Development
|
|
137
|
+
|
|
138
|
+
{{development_notes}}
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
_Generated using Wize Dev Kit `document-project` workflow_
|
|
@@ -28,6 +28,27 @@ Skip:
|
|
|
28
28
|
- `git log --since="1 year ago" --oneline | wc -l` to scope.
|
|
29
29
|
- Any prior README / ARCHITECTURE / docs that exist.
|
|
30
30
|
|
|
31
|
+
## CLI usage
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
wize-dev-kit document-project # quick baseline (default)
|
|
35
|
+
wize-dev-kit document-project quick # same as default
|
|
36
|
+
wize-dev-kit document-project initial_scan # pattern-only initial scan
|
|
37
|
+
wize-dev-kit document-project initial_scan deep # reads critical directories
|
|
38
|
+
wize-dev-kit document-project initial_scan exhaustive # reads all source files in batches
|
|
39
|
+
wize-dev-kit document-project full_rescan # archives old state, re-runs initial_scan
|
|
40
|
+
wize-dev-kit document-project deep_dive --target src/tools/installer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Modes
|
|
44
|
+
|
|
45
|
+
| Mode | What it does | Scan levels |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `quick` | Writes the 6 baseline files. Does not read source files. | `quick` only |
|
|
48
|
+
| `initial_scan` | Classifies project type, writes index + overview + source tree + conditional docs. | `quick`, `deep`, `exhaustive` |
|
|
49
|
+
| `full_rescan` | Archives `project-scan-report.json` and re-runs `initial_scan`. | `quick`, `deep`, `exhaustive` |
|
|
50
|
+
| `deep_dive` | Exhaustive analysis of a specific folder/file/feature. | `exhaustive` |
|
|
51
|
+
|
|
31
52
|
## Outputs
|
|
32
53
|
|
|
33
54
|
- `.wize/knowledge/document-project/overview.md` — what the project is, who uses it, how big it is.
|
|
@@ -36,6 +57,8 @@ Skip:
|
|
|
36
57
|
- `.wize/knowledge/document-project/dependencies.md` — runtime deps + dev deps + their roles.
|
|
37
58
|
- `.wize/knowledge/document-project/risk-spots.md` — areas of concentrated complexity, undocumented behavior, or known fragility.
|
|
38
59
|
- `.wize/knowledge/document-project/open-questions.md` — things the code doesn't answer; route to humans.
|
|
60
|
+
- `.wize/knowledge/document-project/index.md` — master navigation with `_(To be generated)_` markers for missing conditional docs.
|
|
61
|
+
- `.wize/knowledge/document-project/project-scan-report.json` — resume state.
|
|
39
62
|
|
|
40
63
|
## Steps
|
|
41
64
|
|
|
@@ -19,6 +19,7 @@ Hawkeye drives. Tony co-signs. Runs **once**, right after architecture is signed
|
|
|
19
19
|
- `.wize/solutioning/epics/`
|
|
20
20
|
- `.wize/planning/nfr-principles.md`
|
|
21
21
|
- `.wize/knowledge/document-project/risk-spots.md` (brownfield)
|
|
22
|
+
- `.wize/knowledge/document-project/index.md` (documentation gap markers)
|
|
22
23
|
|
|
23
24
|
## Output
|
|
24
25
|
|
|
@@ -115,6 +116,16 @@ The narrative explains the matrix; the YAML is the structured truth. Hawkeye wri
|
|
|
115
116
|
- Other stories follow the default 70/20/10 split.
|
|
116
117
|
```
|
|
117
118
|
|
|
119
|
+
## Documentation gap risk category
|
|
120
|
+
|
|
121
|
+
In brownfield repos, missing or stale documentation is an operational risk: new contributors make unsafe assumptions, AI agents hallucinate context, and reviews miss implicit contracts. Hawkeye treats it as a first-class risk area.
|
|
122
|
+
|
|
123
|
+
| Symptom | Severity | Rationale | Mitigation |
|
|
124
|
+
|---|---|---|---|
|
|
125
|
+
| `index.md` contains `_(To be generated)_` markers | medium | Conditional docs were skipped; the baseline is incomplete. | Schedule `wize-document-project initial_scan` for the next cooldown. |
|
|
126
|
+
| Baseline files older than 60 days | medium | Docs may no longer reflect the codebase. | Run `wize-refresh-knowledge` at sprint end. |
|
|
127
|
+
| No baseline exists in a brownfield repo | high | Team is flying blind; architecture and conventions are tribal knowledge. | Run `wize-dev-kit document-project quick` immediately. |
|
|
128
|
+
|
|
118
129
|
## Anti-patterns Hawkeye rejects
|
|
119
130
|
|
|
120
131
|
- **Findings without mitigation.** A finding without a contract is a wish.
|
|
@@ -130,7 +130,21 @@ function knowledgeStatus(projectRoot) {
|
|
|
130
130
|
const pendingLines = fileExists(pendingFile)
|
|
131
131
|
? readSafe(pendingFile).split('\n').filter(l => l.trim() && !l.startsWith('#')).length
|
|
132
132
|
: 0;
|
|
133
|
-
|
|
133
|
+
|
|
134
|
+
const indexPath = path.join(root, 'index.md');
|
|
135
|
+
const indexContent = fileExists(indexPath) ? readSafe(indexPath) : '';
|
|
136
|
+
const toBeGeneratedMarkers = (indexContent.match(/_\(To be generated\)_/g) || []).length;
|
|
137
|
+
|
|
138
|
+
const statePath = path.join(root, 'project-scan-report.json');
|
|
139
|
+
const stateExists = fileExists(statePath);
|
|
140
|
+
const stateContent = stateExists ? readSafe(statePath) : '';
|
|
141
|
+
let stateAgeDays = null;
|
|
142
|
+
try {
|
|
143
|
+
const state = JSON.parse(stateContent);
|
|
144
|
+
stateAgeDays = daysAgo(state.timestamps && state.timestamps.last_updated);
|
|
145
|
+
} catch (_) {}
|
|
146
|
+
|
|
147
|
+
return { exists: true, files: refreshed, pendingLines, toBeGeneratedMarkers, stateExists, stateAgeDays };
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
function gitInfo(projectRoot) {
|
|
@@ -247,6 +261,18 @@ async function cmdDoctor({ kitRoot, projectRoot, opts = {} } = {}) {
|
|
|
247
261
|
suggestions.push({ level: 'info', text: `${knowledge.pendingLines} notes piled up in _pending.md. Time to run \`wize-refresh-knowledge\`.` });
|
|
248
262
|
}
|
|
249
263
|
}
|
|
264
|
+
const markerText = knowledge.toBeGeneratedMarkers > 0 ? `${knowledge.toBeGeneratedMarkers} marker(s)` : 'none';
|
|
265
|
+
log(` To be generated markers: ${markerText}`);
|
|
266
|
+
if (knowledge.toBeGeneratedMarkers >= 5) {
|
|
267
|
+
suggestions.push({ level: 'warn', text: `${knowledge.toBeGeneratedMarkers} docs marked "To be generated" in index.md. Run \`wize-dev-kit document-project initial_scan deep\` to fill them.` });
|
|
268
|
+
}
|
|
269
|
+
const stateText = knowledge.stateExists
|
|
270
|
+
? (knowledge.stateAgeDays == null ? 'project-scan-report.json exists (age unknown)' : `project-scan-report.json ${knowledge.stateAgeDays}d old`)
|
|
271
|
+
: 'no project-scan-report.json';
|
|
272
|
+
log(` Scan state: ${stateText}`);
|
|
273
|
+
if (!knowledge.stateExists) {
|
|
274
|
+
suggestions.push({ level: 'info', text: 'No scan state file found. Run `wize-dev-kit document-project quick` to create a baseline + state.' });
|
|
275
|
+
}
|
|
250
276
|
}
|
|
251
277
|
|
|
252
278
|
// -- Section: Harness CLIs --
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// `wize-dev-kit document-project` — CLI command entry point.
|
|
2
|
+
//
|
|
3
|
+
// Parses mode + flags and delegates to the appropriate scan implementation.
|
|
4
|
+
// Non-quick modes are stubs here; later stories fill them in.
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const { runQuick } = require('../document-project/modes/quick.js');
|
|
11
|
+
const { runInitialScan } = require('../document-project/modes/initial-scan.js');
|
|
12
|
+
const { runFullRescan } = require('../document-project/modes/full-rescan.js');
|
|
13
|
+
const { runDeepDive } = require('../document-project/modes/deep-dive.js');
|
|
14
|
+
const { loadState, archiveOldState } = require('../document-project/state.js');
|
|
15
|
+
|
|
16
|
+
const VALID_MODES = new Set(['quick', 'initial_scan', 'full_rescan', 'deep_dive']);
|
|
17
|
+
const VALID_SCAN_LEVELS = new Set(['quick', 'deep', 'exhaustive']);
|
|
18
|
+
|
|
19
|
+
function parseMode(args) {
|
|
20
|
+
let mode = 'quick';
|
|
21
|
+
let resume = false;
|
|
22
|
+
let scanLevel = 'quick';
|
|
23
|
+
let target = null;
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
const arg = args[i];
|
|
27
|
+
if (arg === '--resume') {
|
|
28
|
+
resume = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (arg === '--target') {
|
|
32
|
+
target = args[i + 1];
|
|
33
|
+
i++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (arg.startsWith('--')) {
|
|
37
|
+
return { mode: null, error: `unknown option: ${arg}` };
|
|
38
|
+
}
|
|
39
|
+
if (VALID_SCAN_LEVELS.has(arg)) {
|
|
40
|
+
scanLevel = arg;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (mode === 'quick' && VALID_MODES.has(arg)) {
|
|
44
|
+
mode = arg;
|
|
45
|
+
} else if (mode === 'quick') {
|
|
46
|
+
return { mode: null, error: `unknown mode: ${arg}. Valid modes: ${[...VALID_MODES].join(', ')}` };
|
|
47
|
+
} else {
|
|
48
|
+
return { mode: null, error: `unexpected extra argument: ${arg}` };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!VALID_MODES.has(mode)) {
|
|
53
|
+
return { mode: null, error: `unknown mode: ${mode}. Valid modes: ${[...VALID_MODES].join(', ')}` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { mode, resume, scanLevel, target };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function cmdDocumentProject({ kitRoot, projectRoot, args = [], opts = {} }) {
|
|
60
|
+
const log = opts.log || console.log;
|
|
61
|
+
const parsed = parseMode(args);
|
|
62
|
+
|
|
63
|
+
if (parsed.mode === null) {
|
|
64
|
+
log(parsed.error);
|
|
65
|
+
return { ok: false, error: parsed.error, exitCode: 1 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (parsed.mode === 'quick') {
|
|
69
|
+
const r = runQuick(projectRoot, { log });
|
|
70
|
+
return { ok: true, mode: 'quick', ...r };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (parsed.mode === 'initial_scan') {
|
|
74
|
+
const r = runInitialScan(projectRoot, { scanLevel: parsed.scanLevel, log });
|
|
75
|
+
return { ok: true, mode: 'initial_scan', ...r };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (parsed.mode === 'full_rescan') {
|
|
79
|
+
const r = runFullRescan(projectRoot, { scanLevel: parsed.scanLevel, log });
|
|
80
|
+
return { ok: true, mode: 'full_rescan', ...r };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (parsed.mode === 'deep_dive') {
|
|
84
|
+
const r = runDeepDive(projectRoot, { target: parsed.target, log });
|
|
85
|
+
return r;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
log(`document-project ${parsed.mode} not implemented`);
|
|
89
|
+
return { ok: false, error: 'not implemented', exitCode: 1 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { cmdDocumentProject, parseMode };
|
|
93
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Batch scanner for `wize-dev-kit document-project`.
|
|
2
|
+
//
|
|
3
|
+
// Walks a repo in subfolder-sized batches, skipping noise directories.
|
|
4
|
+
// Returns summaries without loading entire trees into memory.
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_IGNORE = new Set([
|
|
12
|
+
'node_modules',
|
|
13
|
+
'.git',
|
|
14
|
+
'dist',
|
|
15
|
+
'build',
|
|
16
|
+
'coverage',
|
|
17
|
+
'.cache',
|
|
18
|
+
'.tmp',
|
|
19
|
+
'.wize',
|
|
20
|
+
'.claude',
|
|
21
|
+
'.cursor',
|
|
22
|
+
'.kimi',
|
|
23
|
+
'.opencode',
|
|
24
|
+
'.windsurf',
|
|
25
|
+
'.continue',
|
|
26
|
+
'.codex',
|
|
27
|
+
'.antigravity'
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const MAX_FILE_LOC = 5000;
|
|
31
|
+
|
|
32
|
+
function shouldIgnore(name, customIgnore = []) {
|
|
33
|
+
if (DEFAULT_IGNORE.has(name)) return true;
|
|
34
|
+
for (const pattern of customIgnore) {
|
|
35
|
+
if (name === pattern || (pattern.startsWith('*') && name.endsWith(pattern.slice(1)))) return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function listSubfolders(rootDir, options = {}) {
|
|
41
|
+
const out = [];
|
|
42
|
+
let entries;
|
|
43
|
+
try { entries = fs.readdirSync(rootDir, { withFileTypes: true }); } catch { return out; }
|
|
44
|
+
for (const e of entries) {
|
|
45
|
+
if (!e.isDirectory()) continue;
|
|
46
|
+
if (shouldIgnore(e.name, options.ignore)) continue;
|
|
47
|
+
out.push(path.join(rootDir, e.name));
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scanFolder(folderPath, options = {}) {
|
|
53
|
+
const files = [];
|
|
54
|
+
let totalLoc = 0;
|
|
55
|
+
const stack = [folderPath];
|
|
56
|
+
while (stack.length) {
|
|
57
|
+
const dir = stack.pop();
|
|
58
|
+
let entries;
|
|
59
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
60
|
+
for (const e of entries) {
|
|
61
|
+
const full = path.join(dir, e.name);
|
|
62
|
+
if (e.isDirectory()) {
|
|
63
|
+
if (!shouldIgnore(e.name, options.ignore)) stack.push(full);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!e.isFile()) continue;
|
|
67
|
+
if (/\.(min\.js|map)$/.test(e.name)) continue;
|
|
68
|
+
const stat = fs.statSync(full);
|
|
69
|
+
const loc = stat.size; // proxy; exact line count is optional
|
|
70
|
+
const info = {
|
|
71
|
+
path: full,
|
|
72
|
+
relative: path.relative(folderPath, full),
|
|
73
|
+
size: stat.size,
|
|
74
|
+
loc: loc > MAX_FILE_LOC ? MAX_FILE_LOC : loc,
|
|
75
|
+
skipped: loc > MAX_FILE_LOC
|
|
76
|
+
};
|
|
77
|
+
files.push(info);
|
|
78
|
+
totalLoc += info.loc;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { folder: folderPath, files, totalLoc, fileCount: files.length };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function batchScanner(rootDir, options = {}) {
|
|
85
|
+
const folders = options.folders || listSubfolders(rootDir, options);
|
|
86
|
+
const results = [];
|
|
87
|
+
for (const folder of folders) {
|
|
88
|
+
results.push(scanFolder(folder, options));
|
|
89
|
+
}
|
|
90
|
+
return results;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { batchScanner, listSubfolders, scanFolder, shouldIgnore, MAX_FILE_LOC };
|