@ryuenn3123/agentic-senior-core 2.5.21 → 3.0.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/.agent-context/prompts/init-project.md +5 -5
- package/.agent-context/prompts/refactor.md +2 -1
- package/.agent-context/prompts/review-code.md +3 -2
- package/.agent-context/review-checklists/pr-checklist.md +8 -1
- package/.agent-context/rules/architecture.md +11 -0
- package/.agent-context/rules/frontend-architecture.md +2 -2
- package/.agent-context/state/architecture-map.md +1 -1
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.agents/workflows/init-project.md +3 -3
- package/.agents/workflows/refactor.md +1 -1
- package/.agents/workflows/review-code.md +4 -5
- package/.cursorrules +27 -71
- package/.gemini/instructions.md +6 -7
- package/.github/copilot-instructions.md +5 -6
- package/.windsurfrules +27 -71
- package/AGENTS.md +7 -9
- package/CONTRIBUTING.md +18 -31
- package/README.md +28 -4
- package/bin/agentic-senior-core.js +0 -6
- package/lib/cli/commands/init.mjs +142 -655
- package/lib/cli/commands/launch.mjs +1 -23
- package/lib/cli/commands/rollback.mjs +1 -1
- package/lib/cli/commands/upgrade.mjs +1 -23
- package/lib/cli/compiler.mjs +116 -70
- package/lib/cli/constants.mjs +84 -26
- package/lib/cli/init-architecture-flow.mjs +231 -0
- package/lib/cli/init-detection-flow.mjs +123 -0
- package/lib/cli/init-options.mjs +344 -0
- package/lib/cli/init-selection.mjs +100 -0
- package/lib/cli/preflight.mjs +1 -1
- package/lib/cli/profile-packs.mjs +15 -1
- package/lib/cli/project-scaffolder.mjs +209 -161
- package/lib/cli/utils.mjs +17 -13
- package/mcp.json +19 -19
- package/package.json +5 -2
- package/scripts/context-triggered-audit.mjs +18 -18
- package/scripts/documentation-boundary-audit.mjs +92 -5
- package/scripts/forbidden-content-check.mjs +1 -1
- package/scripts/frontend-usability-audit.mjs +21 -28
- package/scripts/governance-weekly-report.mjs +29 -15
- package/scripts/llm-judge.mjs +2 -5
- package/scripts/mcp-server.mjs +389 -5
- package/scripts/release-gate.mjs +121 -145
- package/scripts/sync-thin-adapters.mjs +161 -0
- package/scripts/v3-purge-audit.mjs +231 -0
- package/scripts/validate-evidence-bundle.mjs +1 -1
- package/scripts/validate.mjs +224 -272
- package/.agent-context/blueprints/api-nextjs.md +0 -184
- package/.agent-context/blueprints/aspnet-api.md +0 -247
- package/.agent-context/blueprints/ci-github-actions.md +0 -226
- package/.agent-context/blueprints/ci-gitlab.md +0 -200
- package/.agent-context/blueprints/fastapi-service.md +0 -210
- package/.agent-context/blueprints/go-service.md +0 -217
- package/.agent-context/blueprints/graphql-grpc-api.md +0 -51
- package/.agent-context/blueprints/infrastructure-as-code.md +0 -62
- package/.agent-context/blueprints/kubernetes-manifests.md +0 -76
- package/.agent-context/blueprints/laravel-api.md +0 -233
- package/.agent-context/blueprints/mobile-app.md +0 -91
- package/.agent-context/blueprints/nestjs-logic.md +0 -247
- package/.agent-context/blueprints/observability.md +0 -227
- package/.agent-context/blueprints/spring-boot-api.md +0 -218
- package/.agent-context/profiles/platform.md +0 -13
- package/.agent-context/profiles/regulated.md +0 -13
- package/.agent-context/profiles/startup.md +0 -13
- package/.agent-context/review-checklists/frontend-excellence-rubric.md +0 -73
- package/.agent-context/review-checklists/frontend-skill-parity.md +0 -29
- package/.agent-context/review-checklists/frontend-usability.md +0 -35
- package/.agent-context/review-checklists/marketplace-acceptance.md +0 -60
- package/.agent-context/review-checklists/performance-audit.md +0 -71
- package/.agent-context/review-checklists/release-operations.md +0 -33
- package/.agent-context/review-checklists/security-audit.md +0 -119
- package/.agent-context/skills/README.md +0 -63
- package/.agent-context/skills/backend/README.md +0 -68
- package/.agent-context/skills/backend/architecture.md +0 -361
- package/.agent-context/skills/backend/compatibility-manifest.json +0 -8
- package/.agent-context/skills/backend/data-access.md +0 -231
- package/.agent-context/skills/backend/errors.md +0 -138
- package/.agent-context/skills/backend/validation.md +0 -117
- package/.agent-context/skills/backend.md +0 -29
- package/.agent-context/skills/cli/.evidence/compatibility-manifest.json +0 -5
- package/.agent-context/skills/cli/.evidence/sbom-excerpt.json +0 -10
- package/.agent-context/skills/cli/.evidence/test-report.json +0 -8
- package/.agent-context/skills/cli/CHANGELOG.md +0 -6
- package/.agent-context/skills/cli/README.md +0 -56
- package/.agent-context/skills/cli/compatibility-manifest.json +0 -8
- package/.agent-context/skills/cli/init.md +0 -38
- package/.agent-context/skills/cli/output.md +0 -36
- package/.agent-context/skills/cli/package.json +0 -5
- package/.agent-context/skills/cli/safety-telemetry.md +0 -39
- package/.agent-context/skills/cli/tests/.gitkeep +0 -1
- package/.agent-context/skills/cli/upgrade.md +0 -38
- package/.agent-context/skills/cli.md +0 -32
- package/.agent-context/skills/distribution/.evidence/compatibility-manifest.json +0 -9
- package/.agent-context/skills/distribution/.evidence/sbom-excerpt.json +0 -6
- package/.agent-context/skills/distribution/.evidence/test-report.json +0 -8
- package/.agent-context/skills/distribution/CHANGELOG.md +0 -7
- package/.agent-context/skills/distribution/README.md +0 -27
- package/.agent-context/skills/distribution/compatibility-manifest.json +0 -8
- package/.agent-context/skills/distribution/compatibility.md +0 -32
- package/.agent-context/skills/distribution/package.json +0 -5
- package/.agent-context/skills/distribution/provenance-attestation.md +0 -47
- package/.agent-context/skills/distribution/publish.md +0 -37
- package/.agent-context/skills/distribution/rollback.md +0 -32
- package/.agent-context/skills/distribution/tests/.gitkeep +0 -1
- package/.agent-context/skills/distribution.md +0 -32
- package/.agent-context/skills/frontend/.evidence/compatibility-manifest.json +0 -9
- package/.agent-context/skills/frontend/.evidence/sbom-excerpt.json +0 -6
- package/.agent-context/skills/frontend/.evidence/test-report.json +0 -8
- package/.agent-context/skills/frontend/CHANGELOG.md +0 -7
- package/.agent-context/skills/frontend/README.md +0 -50
- package/.agent-context/skills/frontend/accessibility.md +0 -107
- package/.agent-context/skills/frontend/compatibility-manifest.json +0 -8
- package/.agent-context/skills/frontend/conversion-clarity.md +0 -51
- package/.agent-context/skills/frontend/motion.md +0 -67
- package/.agent-context/skills/frontend/package.json +0 -5
- package/.agent-context/skills/frontend/performance.md +0 -63
- package/.agent-context/skills/frontend/responsive-delivery.md +0 -41
- package/.agent-context/skills/frontend/tests/.gitkeep +0 -1
- package/.agent-context/skills/frontend/ui-architecture.md +0 -128
- package/.agent-context/skills/frontend.md +0 -40
- package/.agent-context/skills/fullstack/.evidence/compatibility-manifest.json +0 -9
- package/.agent-context/skills/fullstack/.evidence/sbom-excerpt.json +0 -6
- package/.agent-context/skills/fullstack/.evidence/test-report.json +0 -8
- package/.agent-context/skills/fullstack/CHANGELOG.md +0 -7
- package/.agent-context/skills/fullstack/README.md +0 -27
- package/.agent-context/skills/fullstack/compatibility-manifest.json +0 -8
- package/.agent-context/skills/fullstack/contracts.md +0 -53
- package/.agent-context/skills/fullstack/end-to-end.md +0 -42
- package/.agent-context/skills/fullstack/feature-slicing.md +0 -65
- package/.agent-context/skills/fullstack/package.json +0 -5
- package/.agent-context/skills/fullstack/release-coordination.md +0 -51
- package/.agent-context/skills/fullstack/tests/.gitkeep +0 -1
- package/.agent-context/skills/fullstack.md +0 -30
- package/.agent-context/skills/index.json +0 -107
- package/.agent-context/skills/review-quality/.evidence/compatibility-manifest.json +0 -9
- package/.agent-context/skills/review-quality/.evidence/sbom-excerpt.json +0 -6
- package/.agent-context/skills/review-quality/.evidence/test-report.json +0 -8
- package/.agent-context/skills/review-quality/CHANGELOG.md +0 -7
- package/.agent-context/skills/review-quality/README.md +0 -27
- package/.agent-context/skills/review-quality/benchmark.md +0 -30
- package/.agent-context/skills/review-quality/compatibility-manifest.json +0 -8
- package/.agent-context/skills/review-quality/package.json +0 -5
- package/.agent-context/skills/review-quality/planning.md +0 -38
- package/.agent-context/skills/review-quality/release-decision.md +0 -49
- package/.agent-context/skills/review-quality/security.md +0 -34
- package/.agent-context/skills/review-quality/tests/.gitkeep +0 -1
- package/.agent-context/skills/review-quality.md +0 -34
- package/.agent-context/stacks/csharp.md +0 -149
- package/.agent-context/stacks/flutter.md +0 -16
- package/.agent-context/stacks/go.md +0 -181
- package/.agent-context/stacks/java.md +0 -135
- package/.agent-context/stacks/php.md +0 -192
- package/.agent-context/stacks/python.md +0 -153
- package/.agent-context/stacks/react-native.md +0 -16
- package/.agent-context/stacks/ruby.md +0 -80
- package/.agent-context/stacks/rust.md +0 -86
- package/.agent-context/stacks/typescript.md +0 -317
- package/.agent-context/state/skill-platform.json +0 -38
- package/lib/cli/skill-selector.mjs +0 -232
- package/lib/cli/templates/api-contract.md.id.tmpl +0 -143
- package/lib/cli/templates/api-contract.md.tmpl +0 -143
- package/lib/cli/templates/architecture-decision-record.md.id.tmpl +0 -106
- package/lib/cli/templates/architecture-decision-record.md.tmpl +0 -145
- package/lib/cli/templates/database-schema.md.id.tmpl +0 -74
- package/lib/cli/templates/database-schema.md.tmpl +0 -74
- package/lib/cli/templates/flow-overview.md.id.tmpl +0 -118
- package/lib/cli/templates/flow-overview.md.tmpl +0 -131
- package/lib/cli/templates/project-brief.md.id.tmpl +0 -55
- package/lib/cli/templates/project-brief.md.tmpl +0 -79
- package/scripts/init-project.ps1 +0 -105
- package/scripts/init-project.sh +0 -131
- package/scripts/skill-tier-policy.mjs +0 -76
- package/scripts/trust-scorer.mjs +0 -119
package/lib/cli/utils.mjs
CHANGED
|
@@ -25,7 +25,6 @@ export function printUsage() {
|
|
|
25
25
|
console.log(' npx @ryuenn3123/agentic-senior-core init');
|
|
26
26
|
console.log(' npm install -g @ryuenn3123/agentic-senior-core && agentic-senior-core init');
|
|
27
27
|
console.log(' bunx @ryuenn3123/agentic-senior-core init # optional Bun path');
|
|
28
|
-
console.log(' open GitHub template: https://github.com/fatidaprilian/Agentic-Senior-Core/generate');
|
|
29
28
|
console.log('');
|
|
30
29
|
console.log('Usage:');
|
|
31
30
|
console.log(' agentic-senior-core launch');
|
|
@@ -34,7 +33,6 @@ export function printUsage() {
|
|
|
34
33
|
console.log(' agentic-senior-core optimize [target-directory] [--agent <copilot|claude|cursor|windsurf|gemini|codex|cline>] [--enable|--disable] [--show]');
|
|
35
34
|
console.log(' agentic-senior-core mcp');
|
|
36
35
|
console.log(' agentic-senior-core rollback [target-directory]');
|
|
37
|
-
console.log(' agentic-senior-core skill [domain] [--tier <standard|advance|expert|above>] [--json]');
|
|
38
36
|
console.log(' agentic-senior-core --version');
|
|
39
37
|
console.log('');
|
|
40
38
|
console.log('Options:');
|
|
@@ -63,7 +61,7 @@ export function printUsage() {
|
|
|
63
61
|
console.log(' --no-mcp-template Disable automatic MCP configuration across your IDEs');
|
|
64
62
|
console.log(' --scaffold-docs Force project documentation scaffolding (architecture, database, API, flow)');
|
|
65
63
|
console.log(' --no-scaffold-docs Skip project documentation scaffolding');
|
|
66
|
-
console.log(' --docs-lang Optional override for
|
|
64
|
+
console.log(' --docs-lang Optional override for bootstrap docs synthesis language (default: en)');
|
|
67
65
|
console.log(' --project-config Path to a project config file for non-interactive doc scaffolding');
|
|
68
66
|
console.log(' --runtime-env Override runtime environment hint (auto, linux-wsl, linux, windows, macos)');
|
|
69
67
|
console.log(' --dry-run Preview upgrade without writing files');
|
|
@@ -72,8 +70,6 @@ export function printUsage() {
|
|
|
72
70
|
console.log(' --enable Enable token optimization policy and rebuild compiled rules');
|
|
73
71
|
console.log(' --disable Disable token optimization policy and rebuild compiled rules');
|
|
74
72
|
console.log(' --show Print current token optimization state as JSON');
|
|
75
|
-
console.log(' --tier Choose a skill tier for the skill selector');
|
|
76
|
-
console.log(' --json Emit machine-readable skill selection output');
|
|
77
73
|
}
|
|
78
74
|
|
|
79
75
|
export async function pathExists(targetPath) {
|
|
@@ -199,7 +195,7 @@ export async function copyGovernanceAssetsToTarget(
|
|
|
199
195
|
parsedZedSettings.context_servers['agentic-senior-core'] = zedMcpConfig.context_servers['agentic-senior-core'];
|
|
200
196
|
await fs.writeFile(zedSettingsPath, JSON.stringify(parsedZedSettings, null, 2) + '\n', 'utf8');
|
|
201
197
|
}
|
|
202
|
-
} catch
|
|
198
|
+
} catch {
|
|
203
199
|
// Fallback or ignore if user has broken JSON
|
|
204
200
|
}
|
|
205
201
|
}
|
|
@@ -216,7 +212,7 @@ export async function copyGovernanceAssetsToTarget(
|
|
|
216
212
|
if (await pathExists(globalGeminiMcpPath)) {
|
|
217
213
|
const content = await fs.readFile(globalGeminiMcpPath, 'utf8');
|
|
218
214
|
if (content.trim()) {
|
|
219
|
-
try { geminiConfig = JSON.parse(content); } catch
|
|
215
|
+
try { geminiConfig = JSON.parse(content); } catch {}
|
|
220
216
|
}
|
|
221
217
|
}
|
|
222
218
|
if (!geminiConfig.mcpServers) geminiConfig.mcpServers = {};
|
|
@@ -233,7 +229,7 @@ export async function copyGovernanceAssetsToTarget(
|
|
|
233
229
|
|
|
234
230
|
await fs.writeFile(globalGeminiMcpPath, JSON.stringify(geminiConfig, null, 2) + '\n', 'utf8');
|
|
235
231
|
}
|
|
236
|
-
} catch
|
|
232
|
+
} catch {
|
|
237
233
|
// Ignore global injection errors
|
|
238
234
|
}
|
|
239
235
|
}
|
|
@@ -346,11 +342,19 @@ export function parseBlockingSeverities(rawSeverityValues, fileName) {
|
|
|
346
342
|
}
|
|
347
343
|
|
|
348
344
|
export async function collectFileNames(folderPath) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
345
|
+
try {
|
|
346
|
+
const fileNames = await fs.readdir(folderPath, { withFileTypes: true });
|
|
347
|
+
return fileNames
|
|
348
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
349
|
+
.map((entry) => entry.name)
|
|
350
|
+
.sort((leftName, rightName) => leftName.localeCompare(rightName));
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (error?.code === 'ENOENT') {
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
354
358
|
}
|
|
355
359
|
|
|
356
360
|
export function formatBlockingSeverities(blockingSeverities) {
|
package/mcp.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "1.0",
|
|
3
3
|
"name": "agentic-senior-core",
|
|
4
|
-
"description": "MCP configuration for governance-aware diagnostics and self-healing workflows with
|
|
4
|
+
"description": "MCP configuration for governance-aware diagnostics and self-healing workflows with dynamic knowledge injection.",
|
|
5
5
|
"knowledgeLayers": {
|
|
6
6
|
"enabled": true,
|
|
7
|
-
"description": "8-layer
|
|
7
|
+
"description": "8-layer dynamic knowledge injection for AI agents",
|
|
8
8
|
"layers": {
|
|
9
9
|
"rules": {
|
|
10
10
|
"path": ".agent-context/rules",
|
|
11
11
|
"count": 14,
|
|
12
12
|
"autoLoad": true
|
|
13
13
|
},
|
|
14
|
-
"
|
|
15
|
-
"path": "
|
|
16
|
-
"count":
|
|
14
|
+
"stack-strategies": {
|
|
15
|
+
"path": "dynamic",
|
|
16
|
+
"count": 0,
|
|
17
17
|
"autoLoad": true
|
|
18
18
|
},
|
|
19
|
-
"
|
|
20
|
-
"path": "
|
|
21
|
-
"count":
|
|
19
|
+
"architecture-playbooks": {
|
|
20
|
+
"path": "dynamic",
|
|
21
|
+
"count": 0,
|
|
22
22
|
"autoLoad": true
|
|
23
23
|
},
|
|
24
|
-
"
|
|
25
|
-
"path": "
|
|
26
|
-
"count":
|
|
24
|
+
"execution-contracts": {
|
|
25
|
+
"path": "dynamic",
|
|
26
|
+
"count": 0,
|
|
27
27
|
"autoLoad": true,
|
|
28
|
-
"
|
|
28
|
+
"sources": ["prompts", "review-checklists", "policies"]
|
|
29
29
|
},
|
|
30
30
|
"prompts": {
|
|
31
31
|
"path": ".agent-context/prompts",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"autoLoad": true,
|
|
34
34
|
"templates": ["init-project", "refactor", "review-code"]
|
|
35
35
|
},
|
|
36
|
-
"
|
|
37
|
-
"path": "
|
|
38
|
-
"count":
|
|
36
|
+
"governance-modes": {
|
|
37
|
+
"path": "dynamic",
|
|
38
|
+
"count": 0,
|
|
39
39
|
"autoLoad": true,
|
|
40
40
|
"governance": ["platform", "regulated", "startup"]
|
|
41
41
|
},
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"steps": [
|
|
79
79
|
"load_all_knowledge_layers",
|
|
80
80
|
"inject_rules",
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
81
|
+
"inject_stack_strategies",
|
|
82
|
+
"inject_architecture_playbooks",
|
|
83
|
+
"inject_execution_contracts",
|
|
84
84
|
"inject_prompts",
|
|
85
|
-
"
|
|
85
|
+
"inject_governance_modes",
|
|
86
86
|
"inject_state",
|
|
87
87
|
"inject_policies"
|
|
88
88
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryuenn3123/agentic-senior-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Force your AI Agent to code like a Staff Engineer, not a Junior.",
|
|
6
6
|
"bin": {
|
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
"audit:rules-guardian": "node ./scripts/rules-guardian-audit.mjs",
|
|
49
49
|
"audit:explain-on-demand": "node ./scripts/explain-on-demand-audit.mjs",
|
|
50
50
|
"audit:single-source-lazy-loading": "node ./scripts/single-source-lazy-loading-audit.mjs",
|
|
51
|
+
"audit:v3-purge": "node ./scripts/v3-purge-audit.mjs",
|
|
52
|
+
"sync:adapters": "node ./scripts/sync-thin-adapters.mjs",
|
|
53
|
+
"check:adapters": "node ./scripts/sync-thin-adapters.mjs --check",
|
|
51
54
|
"gate:release": "node ./scripts/release-gate.mjs && node ./scripts/forbidden-content-check.mjs",
|
|
52
55
|
"prepublishOnly": "npm run gate:release",
|
|
53
56
|
"sbom:generate": "node ./scripts/generate-sbom.mjs",
|
|
@@ -62,6 +65,6 @@
|
|
|
62
65
|
"report:docs-quality-drift": "node ./scripts/docs-quality-drift-report.mjs",
|
|
63
66
|
"report:governance-weekly": "node ./scripts/governance-weekly-report.mjs",
|
|
64
67
|
"validate": "node ./scripts/validate.mjs",
|
|
65
|
-
"test": "node --test ./tests/cli-smoke.test.mjs ./tests/mcp-server.test.mjs ./tests/llm-judge.test.mjs ./tests/enterprise-ops.test.mjs
|
|
68
|
+
"test": "node --test ./tests/cli-smoke.test.mjs ./tests/mcp-server.test.mjs ./tests/llm-judge.test.mjs ./tests/enterprise-ops.test.mjs"
|
|
66
69
|
}
|
|
67
70
|
}
|
|
@@ -16,8 +16,8 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
16
16
|
const __dirname = dirname(__filename);
|
|
17
17
|
const REPOSITORY_ROOT = resolve(__dirname, '..');
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
const
|
|
19
|
+
const PR_CHECKLIST_PATH = '.agent-context/review-checklists/pr-checklist.md';
|
|
20
|
+
const ARCHITECTURE_CHECKLIST_PATH = '.agent-context/review-checklists/architecture-review.md';
|
|
21
21
|
const DEFAULT_WORKFLOW = 'auto';
|
|
22
22
|
const SMALL_EDIT_MAX_FILES = 3;
|
|
23
23
|
const MAJOR_FEATURE_MIN_SIGNIFICANT_FILES = 4;
|
|
@@ -36,18 +36,18 @@ const SUPPORTED_WORKFLOWS = new Set([
|
|
|
36
36
|
'standard',
|
|
37
37
|
]);
|
|
38
38
|
|
|
39
|
-
const
|
|
40
|
-
'
|
|
41
|
-
'Strict
|
|
42
|
-
'Small edits
|
|
43
|
-
'User can force strict mode manually
|
|
39
|
+
const REQUIRED_PR_CHECKLIST_SNIPPETS = [
|
|
40
|
+
'### 11. Context-Triggered Audit Mode',
|
|
41
|
+
'Strict audit mode activates automatically on review and PR-intent workflows',
|
|
42
|
+
'Small edits avoid heavy checks by default unless strict mode is explicitly requested',
|
|
43
|
+
'User can always force strict audit mode manually',
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
-
const
|
|
47
|
-
'##
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
46
|
+
const REQUIRED_ARCHITECTURE_CHECKLIST_SNIPPETS = [
|
|
47
|
+
'## Backend Universal Principles',
|
|
48
|
+
'No clever hacks in backend and shared core modules',
|
|
49
|
+
'No premature abstraction',
|
|
50
|
+
'Readability over brevity',
|
|
51
51
|
];
|
|
52
52
|
|
|
53
53
|
function pushResult(results, isPassed, checkName, details) {
|
|
@@ -348,16 +348,16 @@ function runAudit() {
|
|
|
348
348
|
pushResult(results, true, 'strict-audit-activation', `Strict audit mode activated (${auditMode.triggerReason})`);
|
|
349
349
|
|
|
350
350
|
assertChecklist(
|
|
351
|
-
'
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
'pr',
|
|
352
|
+
PR_CHECKLIST_PATH,
|
|
353
|
+
REQUIRED_PR_CHECKLIST_SNIPPETS,
|
|
354
354
|
failures,
|
|
355
355
|
results
|
|
356
356
|
);
|
|
357
357
|
assertChecklist(
|
|
358
|
-
'
|
|
359
|
-
|
|
360
|
-
|
|
358
|
+
'architecture',
|
|
359
|
+
ARCHITECTURE_CHECKLIST_PATH,
|
|
360
|
+
REQUIRED_ARCHITECTURE_CHECKLIST_SNIPPETS,
|
|
361
361
|
failures,
|
|
362
362
|
results
|
|
363
363
|
);
|
|
@@ -15,6 +15,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = dirname(__filename);
|
|
17
17
|
const REPOSITORY_ROOT = resolve(__dirname, '..');
|
|
18
|
+
const DOCUMENTATION_BOUNDARY_AUDIT_REPORT_VERSION = '2.1.0';
|
|
19
|
+
const AUTO_DOCS_SYNC_SCOPE_PHASE = 'phase-1';
|
|
20
|
+
const AUTO_DOCS_SYNC_SCOPE_BOUNDARIES = [
|
|
21
|
+
'public-surface',
|
|
22
|
+
'api-contract',
|
|
23
|
+
'database-structure',
|
|
24
|
+
];
|
|
18
25
|
|
|
19
26
|
const CORE_DOCUMENTATION_FILES = new Set(['README.md', 'CHANGELOG.md']);
|
|
20
27
|
|
|
@@ -22,6 +29,12 @@ const BOUNDARY_RULES = [
|
|
|
22
29
|
{
|
|
23
30
|
boundaryName: 'public-surface',
|
|
24
31
|
requirement: 'Public surface changes must update README.md, CHANGELOG.md, or docs/* in the same scope.',
|
|
32
|
+
suggestedDocumentationUpdates: [
|
|
33
|
+
'README.md',
|
|
34
|
+
'CHANGELOG.md',
|
|
35
|
+
'docs/architecture-decision-record.md',
|
|
36
|
+
'docs/flow-overview.md',
|
|
37
|
+
],
|
|
25
38
|
trigger(filePath) {
|
|
26
39
|
return /^(bin\/|lib\/|scripts\/)/.test(filePath) && !isDocumentationFilePath(filePath);
|
|
27
40
|
},
|
|
@@ -32,6 +45,12 @@ const BOUNDARY_RULES = [
|
|
|
32
45
|
{
|
|
33
46
|
boundaryName: 'api-contract',
|
|
34
47
|
requirement: 'API endpoint or contract changes must update API/OpenAPI documentation in the same scope.',
|
|
48
|
+
suggestedDocumentationUpdates: [
|
|
49
|
+
'docs/api-contract.md',
|
|
50
|
+
'docs/flow-overview.md',
|
|
51
|
+
'.agent-context/rules/api-docs.md',
|
|
52
|
+
'README.md',
|
|
53
|
+
],
|
|
35
54
|
trigger(filePath) {
|
|
36
55
|
return !isDocumentationFilePath(filePath)
|
|
37
56
|
&& /(api|openapi|contract|controller|route|endpoint)/i.test(filePath);
|
|
@@ -45,6 +64,12 @@ const BOUNDARY_RULES = [
|
|
|
45
64
|
{
|
|
46
65
|
boundaryName: 'database-structure',
|
|
47
66
|
requirement: 'Database structure changes must update schema or migration documentation in the same scope.',
|
|
67
|
+
suggestedDocumentationUpdates: [
|
|
68
|
+
'docs/database-schema.md',
|
|
69
|
+
'docs/flow-overview.md',
|
|
70
|
+
'.agent-context/rules/database-design.md',
|
|
71
|
+
'README.md',
|
|
72
|
+
],
|
|
48
73
|
trigger(filePath) {
|
|
49
74
|
return !isDocumentationFilePath(filePath)
|
|
50
75
|
&& /(database|schema|migration|repository|sql|prisma|typeorm|knex)/i.test(filePath);
|
|
@@ -135,6 +160,9 @@ function isDocumentationFilePath(filePath) {
|
|
|
135
160
|
|
|
136
161
|
function evaluateBoundary(boundaryRule, changedFiles, changedDocumentationFiles) {
|
|
137
162
|
const boundaryChangedFiles = changedFiles.filter((filePath) => boundaryRule.trigger(filePath));
|
|
163
|
+
const expectedDocumentationPaths = Array.isArray(boundaryRule.suggestedDocumentationUpdates)
|
|
164
|
+
? boundaryRule.suggestedDocumentationUpdates
|
|
165
|
+
: [];
|
|
138
166
|
|
|
139
167
|
if (boundaryChangedFiles.length === 0) {
|
|
140
168
|
return {
|
|
@@ -144,12 +172,21 @@ function evaluateBoundary(boundaryRule, changedFiles, changedDocumentationFiles)
|
|
|
144
172
|
passed: true,
|
|
145
173
|
changedFiles: [],
|
|
146
174
|
documentationFiles: [],
|
|
175
|
+
expectedDocumentationPaths,
|
|
176
|
+
missingDocumentationUpdates: false,
|
|
177
|
+
suggestedActions: [],
|
|
147
178
|
details: 'Boundary not triggered by changed scope.',
|
|
148
179
|
};
|
|
149
180
|
}
|
|
150
181
|
|
|
151
182
|
const matchingDocumentationFiles = changedDocumentationFiles.filter((filePath) => boundaryRule.docsMatcher(filePath));
|
|
152
183
|
const boundaryPassed = matchingDocumentationFiles.length > 0;
|
|
184
|
+
const suggestedActions = boundaryPassed
|
|
185
|
+
? []
|
|
186
|
+
: [
|
|
187
|
+
`Update one or more boundary docs: ${expectedDocumentationPaths.join(', ')}`,
|
|
188
|
+
'Re-run scripts/documentation-boundary-audit.mjs before merge.',
|
|
189
|
+
];
|
|
153
190
|
|
|
154
191
|
const details = boundaryPassed
|
|
155
192
|
? `Boundary triggered and synchronized with documentation updates: ${matchingDocumentationFiles.join(', ')}`
|
|
@@ -162,6 +199,9 @@ function evaluateBoundary(boundaryRule, changedFiles, changedDocumentationFiles)
|
|
|
162
199
|
passed: boundaryPassed,
|
|
163
200
|
changedFiles: boundaryChangedFiles,
|
|
164
201
|
documentationFiles: matchingDocumentationFiles,
|
|
202
|
+
expectedDocumentationPaths,
|
|
203
|
+
missingDocumentationUpdates: !boundaryPassed,
|
|
204
|
+
suggestedActions,
|
|
165
205
|
details,
|
|
166
206
|
};
|
|
167
207
|
}
|
|
@@ -175,25 +215,72 @@ function runDocumentationBoundaryAudit() {
|
|
|
175
215
|
evaluateBoundary(boundaryRule, changedFiles, changedDocumentationFiles)
|
|
176
216
|
));
|
|
177
217
|
|
|
178
|
-
const
|
|
218
|
+
const violations = boundaryResults
|
|
179
219
|
.filter((boundaryResult) => boundaryResult.triggered && !boundaryResult.passed)
|
|
180
|
-
.map((boundaryResult) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
220
|
+
.map((boundaryResult) => ({
|
|
221
|
+
boundaryName: boundaryResult.boundaryName,
|
|
222
|
+
requirement: boundaryResult.requirement,
|
|
223
|
+
changedFiles: boundaryResult.changedFiles,
|
|
224
|
+
expectedDocumentationPaths: boundaryResult.expectedDocumentationPaths,
|
|
225
|
+
suggestedActions: boundaryResult.suggestedActions,
|
|
226
|
+
diagnosticCode: `BOUNDARY_${boundaryResult.boundaryName.toUpperCase().replace(/-/g, '_')}_DOCS_SYNC_REQUIRED`,
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
const failures = violations.map((violation) => {
|
|
230
|
+
const affectedFiles = violation.changedFiles.join(', ');
|
|
231
|
+
return `${violation.boundaryName}: ${violation.requirement} Changed files: ${affectedFiles}`;
|
|
232
|
+
});
|
|
184
233
|
|
|
185
234
|
const reportPayload = {
|
|
186
235
|
generatedAt: new Date().toISOString(),
|
|
236
|
+
reportVersion: DOCUMENTATION_BOUNDARY_AUDIT_REPORT_VERSION,
|
|
187
237
|
auditName: 'documentation-boundary-audit',
|
|
188
238
|
source: changedScope.source,
|
|
189
239
|
changedFileCount: changedFiles.length,
|
|
190
240
|
changedFiles,
|
|
191
241
|
boundaryResults,
|
|
242
|
+
violations,
|
|
192
243
|
passed: failures.length === 0,
|
|
193
244
|
failureCount: failures.length,
|
|
194
245
|
failures,
|
|
195
246
|
};
|
|
196
247
|
|
|
248
|
+
const triggeredBoundaryResults = boundaryResults.filter((boundaryResult) => boundaryResult.triggered);
|
|
249
|
+
const passedTriggeredBoundaryResults = triggeredBoundaryResults.filter((boundaryResult) => boundaryResult.passed);
|
|
250
|
+
const scopeMatchedDocumentationFiles = uniqueSorted(
|
|
251
|
+
triggeredBoundaryResults.flatMap((boundaryResult) => boundaryResult.documentationFiles),
|
|
252
|
+
);
|
|
253
|
+
const scopeMatchedDocumentationFileSet = new Set(scopeMatchedDocumentationFiles);
|
|
254
|
+
const outOfScopeDocumentationFiles = changedDocumentationFiles.filter(
|
|
255
|
+
(filePath) => !scopeMatchedDocumentationFileSet.has(filePath),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const precisionNumerator = scopeMatchedDocumentationFiles.length;
|
|
259
|
+
const precisionDenominator = changedDocumentationFiles.length;
|
|
260
|
+
const recallNumerator = passedTriggeredBoundaryResults.length;
|
|
261
|
+
const recallDenominator = triggeredBoundaryResults.length;
|
|
262
|
+
|
|
263
|
+
const precision = precisionDenominator > 0 ? precisionNumerator / precisionDenominator : 1;
|
|
264
|
+
const recall = recallDenominator > 0 ? recallNumerator / recallDenominator : 1;
|
|
265
|
+
|
|
266
|
+
reportPayload.autoDocsSyncScope = {
|
|
267
|
+
phase: AUTO_DOCS_SYNC_SCOPE_PHASE,
|
|
268
|
+
bounded: true,
|
|
269
|
+
explicitBoundaries: AUTO_DOCS_SYNC_SCOPE_BOUNDARIES,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
reportPayload.rolloutMetrics = {
|
|
273
|
+
measuredAt: reportPayload.generatedAt,
|
|
274
|
+
precision,
|
|
275
|
+
recall,
|
|
276
|
+
precisionNumerator,
|
|
277
|
+
precisionDenominator,
|
|
278
|
+
recallNumerator,
|
|
279
|
+
recallDenominator,
|
|
280
|
+
scopeMatchedDocumentationFiles,
|
|
281
|
+
outOfScopeDocumentationFiles,
|
|
282
|
+
};
|
|
283
|
+
|
|
197
284
|
console.log(JSON.stringify(reportPayload, null, 2));
|
|
198
285
|
process.exit(reportPayload.passed ? 0 : 1);
|
|
199
286
|
}
|
|
@@ -21,8 +21,8 @@ const REQUIRED_FILES = [
|
|
|
21
21
|
'docs/v1.7-issue-breakdown.md',
|
|
22
22
|
'docs/v1.7-execution-playbook.md',
|
|
23
23
|
'.agent-context/rules/frontend-architecture.md',
|
|
24
|
-
'.agent-context/review-checklists/
|
|
25
|
-
'.agent-context/review-checklists/
|
|
24
|
+
'.agent-context/review-checklists/pr-checklist.md',
|
|
25
|
+
'.agent-context/review-checklists/architecture-review.md',
|
|
26
26
|
];
|
|
27
27
|
|
|
28
28
|
const REQUIRED_ROADMAP_SNIPPETS = [
|
|
@@ -32,24 +32,17 @@ const REQUIRED_ROADMAP_SNIPPETS = [
|
|
|
32
32
|
'Delivered Scope',
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'Documentation and Release Evidence',
|
|
35
|
+
const REQUIRED_PR_CHECKLIST_SNIPPETS = [
|
|
36
|
+
'### 2. Architecture (→ rules/architecture.md)',
|
|
37
|
+
'### 10. Documentation',
|
|
38
|
+
'### 15. Universal SOP Consolidation',
|
|
40
39
|
];
|
|
41
40
|
|
|
42
|
-
const
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'Language and Content Consistency',
|
|
48
|
-
'Text Contrast and Collision Safety',
|
|
49
|
-
'UX Narrative and Conversion Clarity',
|
|
50
|
-
'Template Diversity and Originality',
|
|
51
|
-
'Low-Diversity Template Output Policy',
|
|
52
|
-
'Awwwards-level reference quality',
|
|
41
|
+
const REQUIRED_ARCHITECTURE_CHECKLIST_SNIPPETS = [
|
|
42
|
+
'## Backend Universal Principles',
|
|
43
|
+
'No clever hacks in backend and shared core modules',
|
|
44
|
+
'No premature abstraction',
|
|
45
|
+
'Readability over brevity',
|
|
53
46
|
];
|
|
54
47
|
|
|
55
48
|
const REQUIRED_FRONTEND_RULE_SNIPPETS = [
|
|
@@ -86,17 +79,17 @@ function runAudit() {
|
|
|
86
79
|
|
|
87
80
|
const roadmapPath = 'docs/roadmap.md';
|
|
88
81
|
const frontendRulePath = '.agent-context/rules/frontend-architecture.md';
|
|
89
|
-
const
|
|
90
|
-
const
|
|
82
|
+
const prChecklistPath = '.agent-context/review-checklists/pr-checklist.md';
|
|
83
|
+
const architectureChecklistPath = '.agent-context/review-checklists/architecture-review.md';
|
|
91
84
|
|
|
92
85
|
if (existsSync(resolve(REPOSITORY_ROOT, roadmapPath))) {
|
|
93
86
|
const roadmapContent = readFileSync(resolve(REPOSITORY_ROOT, roadmapPath), 'utf8');
|
|
94
87
|
assertContains('Roadmap', roadmapPath, roadmapContent, REQUIRED_ROADMAP_SNIPPETS, failures);
|
|
95
88
|
}
|
|
96
89
|
|
|
97
|
-
if (existsSync(resolve(REPOSITORY_ROOT,
|
|
98
|
-
const checklistContent = readFileSync(resolve(REPOSITORY_ROOT,
|
|
99
|
-
assertContains('
|
|
90
|
+
if (existsSync(resolve(REPOSITORY_ROOT, prChecklistPath))) {
|
|
91
|
+
const checklistContent = readFileSync(resolve(REPOSITORY_ROOT, prChecklistPath), 'utf8');
|
|
92
|
+
assertContains('PR checklist', prChecklistPath, checklistContent, REQUIRED_PR_CHECKLIST_SNIPPETS, failures);
|
|
100
93
|
}
|
|
101
94
|
|
|
102
95
|
if (existsSync(resolve(REPOSITORY_ROOT, frontendRulePath))) {
|
|
@@ -104,13 +97,13 @@ function runAudit() {
|
|
|
104
97
|
assertContains('Frontend rule', frontendRulePath, frontendRuleContent, REQUIRED_FRONTEND_RULE_SNIPPETS, failures);
|
|
105
98
|
}
|
|
106
99
|
|
|
107
|
-
if (existsSync(resolve(REPOSITORY_ROOT,
|
|
108
|
-
const excellenceRubricContent = readFileSync(resolve(REPOSITORY_ROOT,
|
|
100
|
+
if (existsSync(resolve(REPOSITORY_ROOT, architectureChecklistPath))) {
|
|
101
|
+
const excellenceRubricContent = readFileSync(resolve(REPOSITORY_ROOT, architectureChecklistPath), 'utf8');
|
|
109
102
|
assertContains(
|
|
110
|
-
'
|
|
111
|
-
|
|
103
|
+
'Architecture checklist',
|
|
104
|
+
architectureChecklistPath,
|
|
112
105
|
excellenceRubricContent,
|
|
113
|
-
|
|
106
|
+
REQUIRED_ARCHITECTURE_CHECKLIST_SNIPPETS,
|
|
114
107
|
failures
|
|
115
108
|
);
|
|
116
109
|
}
|
|
@@ -12,7 +12,6 @@ import fs from 'node:fs/promises';
|
|
|
12
12
|
import { spawnSync } from 'node:child_process';
|
|
13
13
|
import { dirname, join, resolve } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { calculateTrustScore } from './trust-scorer.mjs';
|
|
16
15
|
|
|
17
16
|
const SCRIPT_FILE_PATH = fileURLToPath(import.meta.url);
|
|
18
17
|
const SCRIPT_DIR = dirname(SCRIPT_FILE_PATH);
|
|
@@ -23,7 +22,20 @@ const ARGUMENT_FLAGS = new Set(process.argv.slice(2));
|
|
|
23
22
|
const isStdoutOnlyMode = ARGUMENT_FLAGS.has('--stdout-only');
|
|
24
23
|
const WEEKLY_WINDOW_DAYS = 7;
|
|
25
24
|
const HISTORY_LIMIT = 26;
|
|
26
|
-
const REQUIRED_VERIFIED_DOMAINS = new Set([
|
|
25
|
+
const REQUIRED_VERIFIED_DOMAINS = new Set([
|
|
26
|
+
'canonical-instructions',
|
|
27
|
+
'pr-checklist',
|
|
28
|
+
'architecture-review',
|
|
29
|
+
'mcp-server',
|
|
30
|
+
'state-continuity',
|
|
31
|
+
]);
|
|
32
|
+
const GOVERNANCE_SURFACE_PATHS = {
|
|
33
|
+
'canonical-instructions': '.instructions.md',
|
|
34
|
+
'pr-checklist': '.agent-context/review-checklists/pr-checklist.md',
|
|
35
|
+
'architecture-review': '.agent-context/review-checklists/architecture-review.md',
|
|
36
|
+
'mcp-server': 'scripts/mcp-server.mjs',
|
|
37
|
+
'state-continuity': '.agent-context/state',
|
|
38
|
+
};
|
|
27
39
|
|
|
28
40
|
function readJsonOrNull(filePath) {
|
|
29
41
|
if (!existsSync(filePath)) {
|
|
@@ -139,13 +151,6 @@ function collectCommitSignals(windowDays) {
|
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
async function collectSkillTrustSignals() {
|
|
142
|
-
const skillDirectoryPath = join(REPOSITORY_ROOT, '.agent-context', 'skills');
|
|
143
|
-
const skillDirectoryEntries = await fs.readdir(skillDirectoryPath, { withFileTypes: true });
|
|
144
|
-
const skillDomainNames = skillDirectoryEntries
|
|
145
|
-
.filter((directoryEntry) => directoryEntry.isDirectory())
|
|
146
|
-
.map((directoryEntry) => directoryEntry.name)
|
|
147
|
-
.sort((leftDomainName, rightDomainName) => leftDomainName.localeCompare(rightDomainName));
|
|
148
|
-
|
|
149
154
|
const trustRows = [];
|
|
150
155
|
const tierCounts = {
|
|
151
156
|
verified: 0,
|
|
@@ -153,17 +158,26 @@ async function collectSkillTrustSignals() {
|
|
|
153
158
|
experimental: 0,
|
|
154
159
|
};
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
const sortedDomainNames = Array.from(REQUIRED_VERIFIED_DOMAINS).sort((leftName, rightName) => {
|
|
162
|
+
return leftName.localeCompare(rightName);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
for (const skillDomainName of sortedDomainNames) {
|
|
166
|
+
const relativeSurfacePath = GOVERNANCE_SURFACE_PATHS[skillDomainName];
|
|
167
|
+
const absoluteSurfacePath = join(REPOSITORY_ROOT, relativeSurfacePath);
|
|
168
|
+
const surfaceExists = existsSync(absoluteSurfacePath);
|
|
169
|
+
const trustTier = surfaceExists ? 'verified' : 'experimental';
|
|
170
|
+
const trustScore = surfaceExists ? 100 : 0;
|
|
158
171
|
|
|
159
|
-
if (typeof tierCounts[
|
|
160
|
-
tierCounts[
|
|
172
|
+
if (typeof tierCounts[trustTier] === 'number') {
|
|
173
|
+
tierCounts[trustTier] += 1;
|
|
161
174
|
}
|
|
162
175
|
|
|
163
176
|
trustRows.push({
|
|
164
177
|
domain: skillDomainName,
|
|
165
|
-
tier:
|
|
166
|
-
score:
|
|
178
|
+
tier: trustTier,
|
|
179
|
+
score: trustScore,
|
|
180
|
+
sourcePath: relativeSurfacePath,
|
|
167
181
|
});
|
|
168
182
|
}
|
|
169
183
|
|
package/scripts/llm-judge.mjs
CHANGED
|
@@ -53,9 +53,6 @@ const IS_DRY_RUN = process.argv.includes('--dry-run');
|
|
|
53
53
|
const SHOULD_EMIT_MACHINE_REPORT = process.env.LLM_JUDGE_EMIT_JSON !== 'false';
|
|
54
54
|
const MACHINE_REPORT_PATH = process.env.LLM_JUDGE_OUTPUT_PATH || DEFAULT_MACHINE_REPORT_PATH;
|
|
55
55
|
|
|
56
|
-
/** @type {string[]} Source code file extensions to include in the diff */
|
|
57
|
-
const SOURCE_CODE_EXTENSIONS = ['*.ts', '*.tsx', '*.js', '*.mjs', '*.cjs', '*.py', '*.go', '*.java', '*.cs', '*.rb', '*.php'];
|
|
58
|
-
|
|
59
56
|
/** @type {Record<string, string>} */
|
|
60
57
|
const SEVERITY_NORMALIZATION_TABLE = {
|
|
61
58
|
critical: 'critical',
|
|
@@ -184,12 +181,12 @@ function collectPullRequestDiff() {
|
|
|
184
181
|
console.log(' Source: local HEAD~1..HEAD fallback');
|
|
185
182
|
try {
|
|
186
183
|
return execSync('git diff HEAD~1 HEAD', execOptions);
|
|
187
|
-
} catch
|
|
184
|
+
} catch {
|
|
188
185
|
try {
|
|
189
186
|
// Initial commit has no parent — diff against empty tree
|
|
190
187
|
const emptyTreeSha = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
191
188
|
return execSync(`git diff "${emptyTreeSha}" HEAD`, execOptions);
|
|
192
|
-
} catch
|
|
189
|
+
} catch {
|
|
193
190
|
console.warn(' ⚠️ Unable to execute git diff. Defaulting to empty diff.');
|
|
194
191
|
return '';
|
|
195
192
|
}
|