create-project-arch 1.4.0 → 1.5.0
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 +17 -0
- package/README.md +5 -3
- package/dist/cli.js +65 -3
- package/dist/projectNameValidation.js +25 -0
- package/dist/projectNameValidation.test.js +19 -0
- package/package.json +2 -2
- package/templates/arch-ui/app/work/page.tsx +2 -2
- package/templates/arch-ui/components/graph-canvas.tsx +8 -7
- package/templates/arch-ui/components/workspace-context.tsx +32 -39
- package/templates/arch-ui/eslint.config.js +3 -1
- package/templates/arch-ui/lib/graph-dataset.ts +1 -3
- package/templates/arch-ui/next-env.d.ts +1 -1
- package/templates/arch-ui/package.json +1 -0
- package/templates/gap-closure/GAP_CLOSURE_TEMPLATE.md +2 -0
- package/templates/gap-closure/README.md +4 -2
- package/templates/gap-closure/example-gap-closure.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.0] - 2026-03-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Scaffolded projects now receive 8 bootstrap architecture tasks on `pa init` (previously 5)
|
|
13
|
+
- New bootstrap tasks 006 `define-system-boundaries`, 007 `define-module-model`, and 008 `define-runtime-architecture` included in generated milestone
|
|
14
|
+
- Bootstrap tasks 006–008 carry `discover` and `greenfield` tags for dual-mode authoring guidance
|
|
15
|
+
- `pa milestone status` command available in all scaffolded projects for dependency-aware task blocking visibility
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Updated templates to use `project-arch@1.5.0`
|
|
20
|
+
- Bootstrap task 005 slug updated to `finalize-architecture-foundation` in all scaffolded projects
|
|
21
|
+
- task 005 in generated projects now declares `dependsOn` on tasks 001–004 and 006–008, enforcing correct completion order
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
8
25
|
## [1.4.0] - 2026-03-10
|
|
9
26
|
|
|
10
27
|
### Added
|
package/README.md
CHANGED
|
@@ -223,9 +223,11 @@ Scaffolded projects include closure report artifacts under `architecture/referen
|
|
|
223
223
|
Recommended workflow:
|
|
224
224
|
|
|
225
225
|
1. Complete milestone tasks and decision updates.
|
|
226
|
-
2. Run `pa
|
|
227
|
-
3.
|
|
228
|
-
4.
|
|
226
|
+
2. Run `pa lint frontmatter --fix`.
|
|
227
|
+
3. Run `pnpm lint:md`.
|
|
228
|
+
4. Run `pa check` and `pa report`.
|
|
229
|
+
5. Record closure outcomes in milestone closure report.
|
|
230
|
+
6. Track remaining gaps as follow-on tasks/decisions.
|
|
229
231
|
|
|
230
232
|
### Local Validation Hook Scaffold
|
|
231
233
|
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
8
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
10
|
const commander_1 = require("commander");
|
|
11
|
+
const projectNameValidation_1 = require("./projectNameValidation");
|
|
11
12
|
function getTemplatesRoot() {
|
|
12
13
|
return path_1.default.resolve(__dirname, "../templates");
|
|
13
14
|
}
|
|
@@ -104,6 +105,67 @@ async function wireProjectArchUsage(targetDir) {
|
|
|
104
105
|
await fs_extra_1.default.writeJSON(rootPackageJsonPath, nextPkg, { spaces: 2 });
|
|
105
106
|
await fs_extra_1.default.appendFile(rootPackageJsonPath, "\n");
|
|
106
107
|
}
|
|
108
|
+
async function normalizeTurboSchema(targetDir) {
|
|
109
|
+
const turboConfigPath = path_1.default.join(targetDir, "turbo.json");
|
|
110
|
+
if (!(await fs_extra_1.default.pathExists(turboConfigPath))) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const turboConfig = (await fs_extra_1.default.readJSON(turboConfigPath));
|
|
114
|
+
const existingGlobalEnv = Array.isArray(turboConfig.globalEnv)
|
|
115
|
+
? turboConfig.globalEnv.filter((entry) => typeof entry === "string")
|
|
116
|
+
: [];
|
|
117
|
+
const globalEnv = existingGlobalEnv.includes("PROJECT_ROOT")
|
|
118
|
+
? existingGlobalEnv
|
|
119
|
+
: [...existingGlobalEnv, "PROJECT_ROOT"];
|
|
120
|
+
await fs_extra_1.default.writeJSON(turboConfigPath, {
|
|
121
|
+
...turboConfig,
|
|
122
|
+
$schema: "./node_modules/turbo/schema.json",
|
|
123
|
+
globalEnv,
|
|
124
|
+
}, { spaces: 2 });
|
|
125
|
+
await fs_extra_1.default.appendFile(turboConfigPath, "\n");
|
|
126
|
+
}
|
|
127
|
+
function getAppReadmeContent(title, appDir) {
|
|
128
|
+
return [
|
|
129
|
+
`# ${title}`,
|
|
130
|
+
"",
|
|
131
|
+
`This app lives in \`${appDir}\` and is part of the generated workspace.`,
|
|
132
|
+
"",
|
|
133
|
+
"## Getting Started",
|
|
134
|
+
"",
|
|
135
|
+
"Run from the repository root:",
|
|
136
|
+
"",
|
|
137
|
+
"```bash",
|
|
138
|
+
`pnpm --filter ${appDir.split("/").pop()} dev`,
|
|
139
|
+
"```",
|
|
140
|
+
"",
|
|
141
|
+
"Or run all apps in dev mode:",
|
|
142
|
+
"",
|
|
143
|
+
"```bash",
|
|
144
|
+
"pnpm dev",
|
|
145
|
+
"```",
|
|
146
|
+
"",
|
|
147
|
+
].join("\n");
|
|
148
|
+
}
|
|
149
|
+
async function normalizeGeneratedAppReadmes(targetDir) {
|
|
150
|
+
const readmeTargets = [
|
|
151
|
+
{
|
|
152
|
+
readmePath: path_1.default.join(targetDir, "apps", "web", "README.md"),
|
|
153
|
+
title: "Web App",
|
|
154
|
+
appDir: "apps/web",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
readmePath: path_1.default.join(targetDir, "apps", "docs", "README.md"),
|
|
158
|
+
title: "Docs App",
|
|
159
|
+
appDir: "apps/docs",
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
for (const target of readmeTargets) {
|
|
163
|
+
if (!(await fs_extra_1.default.pathExists(target.readmePath))) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
await fs_extra_1.default.writeFile(target.readmePath, getAppReadmeContent(target.title, target.appDir), "utf8");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
107
169
|
async function scaffoldArchitectureApps(targetDir) {
|
|
108
170
|
const templatesRoot = getTemplatesRoot();
|
|
109
171
|
const archUiTemplate = path_1.default.join(templatesRoot, "arch-ui");
|
|
@@ -339,9 +401,7 @@ async function main() {
|
|
|
339
401
|
if (projectName === "." || projectName === "./") {
|
|
340
402
|
throw new Error("Refusing to scaffold into current directory. Provide a new project directory name.");
|
|
341
403
|
}
|
|
342
|
-
|
|
343
|
-
throw new Error("Invalid project name. Only alphanumeric characters, dashes, and underscores are allowed.");
|
|
344
|
-
}
|
|
404
|
+
(0, projectNameValidation_1.validateProjectName)(projectName);
|
|
345
405
|
const targetDir = path_1.default.resolve(process.cwd(), projectName);
|
|
346
406
|
await ensureTargetDir(targetDir, options.force);
|
|
347
407
|
runCreateTurbo(projectName, options);
|
|
@@ -367,6 +427,8 @@ async function main() {
|
|
|
367
427
|
await scaffoldArchitectureApps(targetDir);
|
|
368
428
|
await upsertArchModulesInMap(targetDir);
|
|
369
429
|
await wireProjectArchUsage(targetDir);
|
|
430
|
+
await normalizeTurboSchema(targetDir);
|
|
431
|
+
await normalizeGeneratedAppReadmes(targetDir);
|
|
370
432
|
const relativeTarget = path_1.default.relative(process.cwd(), targetDir) || ".";
|
|
371
433
|
console.log(`\nCreated project-arch repo at ${relativeTarget}`);
|
|
372
434
|
console.log("\nNext steps:");
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateProjectName = validateProjectName;
|
|
4
|
+
const npmPackageNamePattern = /^[a-z0-9-~][a-z0-9-._~]*$/;
|
|
5
|
+
const npmReservedNames = new Set(["node_modules", "favicon.ico"]);
|
|
6
|
+
function toNpmNameSuggestion(projectName) {
|
|
7
|
+
return projectName
|
|
8
|
+
.trim()
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9-_]+/g, "-");
|
|
11
|
+
}
|
|
12
|
+
function validateProjectName(projectName) {
|
|
13
|
+
if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) {
|
|
14
|
+
throw new Error("Invalid project name. Only alphanumeric characters, dashes, and underscores are allowed.");
|
|
15
|
+
}
|
|
16
|
+
if (projectName !== projectName.toLowerCase()) {
|
|
17
|
+
throw new Error(`Invalid project name '${projectName}'. Use lowercase to produce a valid npm package name (for example: '${toNpmNameSuggestion(projectName)}').`);
|
|
18
|
+
}
|
|
19
|
+
if (projectName.length > 214) {
|
|
20
|
+
throw new Error("Invalid project name. npm package names must be 214 characters or fewer.");
|
|
21
|
+
}
|
|
22
|
+
if (!npmPackageNamePattern.test(projectName) || npmReservedNames.has(projectName)) {
|
|
23
|
+
throw new Error(`Invalid project name '${projectName}'. It must be a valid npm package name.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const projectNameValidation_1 = require("./projectNameValidation");
|
|
5
|
+
(0, vitest_1.describe)("validateProjectName", () => {
|
|
6
|
+
(0, vitest_1.it)("accepts valid lowercase project names", () => {
|
|
7
|
+
(0, vitest_1.expect)(() => (0, projectNameValidation_1.validateProjectName)("project-arch")).not.toThrow();
|
|
8
|
+
(0, vitest_1.expect)(() => (0, projectNameValidation_1.validateProjectName)("project_arch_2")).not.toThrow();
|
|
9
|
+
});
|
|
10
|
+
(0, vitest_1.it)("rejects uppercase names with actionable guidance", () => {
|
|
11
|
+
(0, vitest_1.expect)(() => (0, projectNameValidation_1.validateProjectName)("testProject")).toThrow("Use lowercase to produce a valid npm package name");
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)("rejects invalid characters", () => {
|
|
14
|
+
(0, vitest_1.expect)(() => (0, projectNameValidation_1.validateProjectName)("my project")).toThrow("Only alphanumeric characters, dashes, and underscores are allowed.");
|
|
15
|
+
});
|
|
16
|
+
(0, vitest_1.it)("rejects npm reserved names", () => {
|
|
17
|
+
(0, vitest_1.expect)(() => (0, projectNameValidation_1.validateProjectName)("node_modules")).toThrow("It must be a valid npm package name.");
|
|
18
|
+
});
|
|
19
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-project-arch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Scaffold new projects with Project Arch templates including Next.js apps and component libraries",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"create",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"commander": "^12.1.0",
|
|
39
39
|
"fs-extra": "^11.3.0",
|
|
40
|
-
"project-arch": "^1.
|
|
40
|
+
"project-arch": "^1.5.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/fs-extra": "^11.0.4",
|
|
@@ -118,7 +118,7 @@ export default function WorkPage() {
|
|
|
118
118
|
(Object.entries(filters.edgeTypes) as Array<[keyof typeof filters.edgeTypes, boolean]>)
|
|
119
119
|
.filter(([, enabled]) => enabled)
|
|
120
120
|
.map(([filter]) => filter as GraphEdgeFilter),
|
|
121
|
-
[filters
|
|
121
|
+
[filters],
|
|
122
122
|
);
|
|
123
123
|
const activeAuthorityFilters = useMemo(
|
|
124
124
|
() =>
|
|
@@ -129,7 +129,7 @@ export default function WorkPage() {
|
|
|
129
129
|
)
|
|
130
130
|
.filter(([, enabled]) => enabled)
|
|
131
131
|
.map(([filter]) => filter),
|
|
132
|
-
[filters
|
|
132
|
+
[filters],
|
|
133
133
|
);
|
|
134
134
|
|
|
135
135
|
return (
|
|
@@ -59,6 +59,12 @@ type GraphCanvasProps = {
|
|
|
59
59
|
|
|
60
60
|
export { type GraphFilter, type InspectorNode } from "./graph/graph-types";
|
|
61
61
|
|
|
62
|
+
const VIEW_CANONICAL_TYPES: Record<GraphCanvasViewMode, string[]> = {
|
|
63
|
+
"architecture-map": ["arch_folder", "domain_doc", "architecture_doc", "architecture_model"],
|
|
64
|
+
tasks: ["roadmap_folder", "roadmap_epic", "roadmap_story", "roadmap_task"],
|
|
65
|
+
project: ["project_folder", "app", "package", "module", "component"],
|
|
66
|
+
};
|
|
67
|
+
|
|
62
68
|
export function GraphCanvas({
|
|
63
69
|
data,
|
|
64
70
|
viewMode,
|
|
@@ -124,11 +130,6 @@ export function GraphCanvas({
|
|
|
124
130
|
}, [nodes]);
|
|
125
131
|
|
|
126
132
|
const behavior = GRAPH_VIEW_BEHAVIOR[viewMode];
|
|
127
|
-
const viewCanonicalTypes: Record<GraphCanvasViewMode, string[]> = {
|
|
128
|
-
"architecture-map": ["arch_folder", "domain_doc", "architecture_doc", "architecture_model"],
|
|
129
|
-
tasks: ["roadmap_folder", "roadmap_epic", "roadmap_story", "roadmap_task"],
|
|
130
|
-
project: ["project_folder", "app", "package", "module", "component"],
|
|
131
|
-
};
|
|
132
133
|
const effectiveHopDepth = useMemo(() => {
|
|
133
134
|
const min = Math.min(...behavior.allowedHopDepths);
|
|
134
135
|
const max = Math.max(...behavior.allowedHopDepths);
|
|
@@ -507,7 +508,7 @@ export function GraphCanvas({
|
|
|
507
508
|
if (!stored) return;
|
|
508
509
|
if (selectedNodeId) return;
|
|
509
510
|
const selected = nodes.find((node) => node.id === stored);
|
|
510
|
-
if (selected &&
|
|
511
|
+
if (selected && VIEW_CANONICAL_TYPES[viewMode].includes(selected.data.canonicalType ?? "")) {
|
|
511
512
|
setSelectedNodeId(stored);
|
|
512
513
|
return;
|
|
513
514
|
}
|
|
@@ -516,7 +517,7 @@ export function GraphCanvas({
|
|
|
516
517
|
const candidateId = mapped.source === stored ? mapped.target : mapped.source;
|
|
517
518
|
const candidateNode = nodes.find((node) => node.id === candidateId);
|
|
518
519
|
if (!candidateNode) return;
|
|
519
|
-
if (!
|
|
520
|
+
if (!VIEW_CANONICAL_TYPES[viewMode].includes(candidateNode.data.canonicalType ?? "")) return;
|
|
520
521
|
setSelectedNodeId(candidateId);
|
|
521
522
|
}, [edges, nodes, selectedNodeId, viewMode]);
|
|
522
523
|
|
|
@@ -18,15 +18,6 @@ type WorkspaceFilters = {
|
|
|
18
18
|
hopDepth: number;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
type WorkspacePersistedState = {
|
|
22
|
-
splitPane: boolean;
|
|
23
|
-
leftCollapsed: boolean;
|
|
24
|
-
rightCollapsed: boolean;
|
|
25
|
-
leftWidth: number;
|
|
26
|
-
rightWidth: number;
|
|
27
|
-
filtersByView: Record<GraphViewMode, WorkspaceFilters>;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
21
|
type WorkspaceContextValue = {
|
|
31
22
|
splitPane: boolean;
|
|
32
23
|
leftCollapsed: boolean;
|
|
@@ -127,36 +118,38 @@ export function WorkspaceProvider({ children }: { children: ReactNode }) {
|
|
|
127
118
|
if (typeof parsed.rightWidth === "number") setRightWidth(clamp(parsed.rightWidth, 320, 720));
|
|
128
119
|
if (parsed.filtersByView) {
|
|
129
120
|
const views: GraphViewMode[] = ["architecture-map", "tasks", "project"];
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
121
|
+
setFiltersByView((prev) => {
|
|
122
|
+
const nextByView = { ...prev };
|
|
123
|
+
for (const view of views) {
|
|
124
|
+
const incoming = parsed.filtersByView?.[view];
|
|
125
|
+
if (!incoming) continue;
|
|
126
|
+
const base = defaultFilters();
|
|
127
|
+
nextByView[view] = {
|
|
128
|
+
nodeTypes: {
|
|
129
|
+
domains: incoming.nodeTypes?.domains ?? base.nodeTypes.domains,
|
|
130
|
+
modules: incoming.nodeTypes?.modules ?? base.nodeTypes.modules,
|
|
131
|
+
tasks: incoming.nodeTypes?.tasks ?? base.nodeTypes.tasks,
|
|
132
|
+
decisions: incoming.nodeTypes?.decisions ?? base.nodeTypes.decisions,
|
|
133
|
+
},
|
|
134
|
+
edgeTypes: {
|
|
135
|
+
dependency: incoming.edgeTypes?.dependency ?? base.edgeTypes.dependency,
|
|
136
|
+
"data-flow": incoming.edgeTypes?.["data-flow"] ?? base.edgeTypes["data-flow"],
|
|
137
|
+
blocking: incoming.edgeTypes?.blocking ?? base.edgeTypes.blocking,
|
|
138
|
+
},
|
|
139
|
+
authorityTypes: {
|
|
140
|
+
authoritative:
|
|
141
|
+
incoming.authorityTypes?.authoritative ?? base.authorityTypes.authoritative,
|
|
142
|
+
manual: incoming.authorityTypes?.manual ?? base.authorityTypes.manual,
|
|
143
|
+
inferred: incoming.authorityTypes?.inferred ?? base.authorityTypes.inferred,
|
|
144
|
+
},
|
|
145
|
+
hideCompletedTasks: incoming.hideCompletedTasks ?? base.hideCompletedTasks,
|
|
146
|
+
showExternalDependencies:
|
|
147
|
+
incoming.showExternalDependencies ?? base.showExternalDependencies,
|
|
148
|
+
hopDepth: clamp(incoming.hopDepth ?? base.hopDepth, 0, 3),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return nextByView;
|
|
152
|
+
});
|
|
160
153
|
}
|
|
161
154
|
} catch {
|
|
162
155
|
// Ignore invalid persisted state.
|
|
@@ -67,8 +67,6 @@ async function walkDirectories(
|
|
|
67
67
|
onDirectory: (relativePath: string, parentRelativePath: string | null) => Promise<void> | void,
|
|
68
68
|
maxDepth = 6,
|
|
69
69
|
): Promise<void> {
|
|
70
|
-
const start = path.join(root, relativeDir);
|
|
71
|
-
|
|
72
70
|
async function walk(currentRelative: string, parentRelative: string | null, depth: number) {
|
|
73
71
|
if (depth > maxDepth) return;
|
|
74
72
|
await onDirectory(currentRelative, parentRelative);
|
|
@@ -138,7 +136,7 @@ async function filterGitIgnoredPaths(root: string, paths: string[]): Promise<Set
|
|
|
138
136
|
}
|
|
139
137
|
}
|
|
140
138
|
return ignored;
|
|
141
|
-
} catch
|
|
139
|
+
} catch {
|
|
142
140
|
// If git is unavailable or command fails unexpectedly, do not block graph generation.
|
|
143
141
|
return new Set();
|
|
144
142
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/
|
|
3
|
+
import "./.next/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
- [ ] Architecture docs updated
|
|
25
25
|
- [ ] Roadmap tasks/decisions synchronized
|
|
26
|
+
- [ ] Frontmatter preflight lint passes (`pa lint frontmatter --fix`)
|
|
27
|
+
- [ ] Markdown lint passes (`pnpm lint:md`)
|
|
26
28
|
- [ ] Graph/parity checks pass (`pa check`)
|
|
27
29
|
- [ ] Report diagnostics reviewed (`pa report`)
|
|
28
30
|
|
|
@@ -10,8 +10,10 @@ Create/update a closure report when a milestone reaches completion readiness.
|
|
|
10
10
|
|
|
11
11
|
1. Copy `GAP_CLOSURE_TEMPLATE.md` into the milestone or reference location.
|
|
12
12
|
2. Fill each section with concrete findings and evidence.
|
|
13
|
-
3. Run
|
|
14
|
-
4.
|
|
13
|
+
3. Run `pa lint frontmatter --fix`.
|
|
14
|
+
4. Run `pnpm lint:md`.
|
|
15
|
+
5. Run `pa check` and `pa report`, then record outcomes.
|
|
16
|
+
6. Track unresolved items as follow-on tasks/decisions.
|
|
15
17
|
|
|
16
18
|
## Suggested Locations
|
|
17
19
|
|
|
@@ -19,6 +19,8 @@ Milestone scope is complete, critical architecture artifacts are synchronized, a
|
|
|
19
19
|
|
|
20
20
|
- [x] Architecture docs updated
|
|
21
21
|
- [x] Roadmap tasks/decisions synchronized
|
|
22
|
+
- [x] Frontmatter preflight lint passes (`pa lint frontmatter --fix`)
|
|
23
|
+
- [x] Markdown lint passes (`pnpm lint:md`)
|
|
22
24
|
- [x] Graph/parity checks pass (`pa check`)
|
|
23
25
|
- [x] Report diagnostics reviewed (`pa report`)
|
|
24
26
|
|