@massa-ai/codex-plugin 1.26.0 → 1.29.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/.codex-plugin/plugin.json +1 -1
- package/agent-profiles/balanced/massa-ai-judge.toml +5 -8
- package/agent-profiles/balanced/massa-ai-meta-judge.toml +2 -5
- package/agent-profiles/balanced/massa-ai-navigator.toml +1 -1
- package/agent-profiles/cheap/massa-ai-judge.toml +5 -8
- package/agent-profiles/cheap/massa-ai-meta-judge.toml +2 -5
- package/agent-profiles/cheap/massa-ai-navigator.toml +1 -1
- package/agent-profiles/heavy/massa-ai-judge.toml +5 -8
- package/agent-profiles/heavy/massa-ai-meta-judge.toml +2 -5
- package/agent-profiles/heavy/massa-ai-navigator.toml +1 -1
- package/agent-profiles/home/massa-ai-judge.toml +5 -8
- package/agent-profiles/home/massa-ai-meta-judge.toml +2 -5
- package/agent-profiles/home/massa-ai-navigator.toml +1 -1
- package/agent-profiles/work/massa-ai-judge.toml +5 -8
- package/agent-profiles/work/massa-ai-meta-judge.toml +2 -5
- package/agent-profiles/work/massa-ai-navigator.toml +1 -1
- package/agents/massa-ai-judge.toml +5 -8
- package/agents/massa-ai-meta-judge.toml +2 -5
- package/agents/massa-ai-navigator.toml +1 -1
- package/install.sh +32 -0
- package/package.json +1 -1
- package/skills/agents/judge/SKILL.md +6 -9
- package/skills/agents/meta-judge/SKILL.md +3 -6
- package/skills/agents/navigator/SKILL.md +2 -2
- package/skills/massa-ai/SKILL.md +6 -17
- package/skills/massa-ai/references/adr-authoring.md +3 -3
- package/skills/massa-ai/references/agent-orchestration.md +18 -3
- package/skills/massa-ai/references/architecture-coupling-lens.md +1 -1
- package/skills/massa-ai/references/architecture-deepening-lens.md +1 -1
- package/skills/massa-ai/references/architecture-domain-lens.md +1 -1
- package/skills/massa-ai/references/architecture-lenses.md +1 -1
- package/skills/massa-ai/references/audit-report-io.md +32 -2
- package/skills/massa-ai/references/audit-scope.md +22 -1
- package/skills/massa-ai/references/code-annotation.md +5 -5
- package/skills/massa-ai/references/codebase-investigation.md +1 -1
- package/skills/massa-ai/references/coding-guidelines.md +67 -0
- package/skills/massa-ai/references/context-firewall.md +2 -1
- package/skills/massa-ai/references/conversation-feedback.md +1 -1
- package/skills/massa-ai/references/debug-diagnosis-loop.md +1 -1
- package/skills/massa-ai/references/decision-engine.md +1 -1
- package/skills/massa-ai/references/evidence-gate.md +1 -1
- package/skills/massa-ai/references/figma-pre-analysis.md +3 -3
- package/skills/massa-ai/references/furps/analyst-role.md +1 -1
- package/skills/massa-ai/references/furps/checklist.md +1 -1
- package/skills/massa-ai/references/furps/intake.md +1 -1
- package/skills/massa-ai/references/furps/report-contract.md +1 -1
- package/skills/massa-ai/references/graceful-degradation.md +22 -0
- package/skills/massa-ai/references/hook-enforcement.md +3 -3
- package/skills/massa-ai/references/implementation-delivery.md +4 -4
- package/skills/massa-ai/references/installation.md +1 -1
- package/skills/massa-ai/references/lessons.md +2 -2
- package/skills/massa-ai/references/maestro/artifacts-reports.md +1 -1
- package/skills/massa-ai/references/maestro/cli-device.md +1 -1
- package/skills/massa-ai/references/maestro/cloud.md +1 -1
- package/skills/massa-ai/references/maestro/config-env-output.md +1 -1
- package/skills/massa-ai/references/maestro/fact-ledger.md +1 -1
- package/skills/massa-ai/references/maestro/js-scripting.md +1 -1
- package/skills/massa-ai/references/maestro/mcp.md +1 -1
- package/skills/massa-ai/references/maestro/patterns.md +1 -1
- package/skills/massa-ai/references/maestro/selectors.md +1 -1
- package/skills/massa-ai/references/maestro/workspace-execution.md +1 -1
- package/skills/massa-ai/references/maestro/yaml-commands.md +1 -1
- package/skills/massa-ai/references/maestro.md +1 -1
- package/skills/massa-ai/references/mcp-tools.md +2 -2
- package/skills/massa-ai/references/memory-policy.md +2 -2
- package/skills/massa-ai/references/mobile-context.md +9 -5
- package/skills/massa-ai/references/mobile-diagnosis.md +2 -2
- package/skills/massa-ai/references/mobile-figma-matcher/android-compose.md +1 -1
- package/skills/massa-ai/references/mobile-figma-matcher/android-views.md +1 -1
- package/skills/massa-ai/references/mobile-figma-matcher/core.md +1 -1
- package/skills/massa-ai/references/mobile-figma-matcher/ios-swiftui.md +1 -1
- package/skills/massa-ai/references/mobile-figma-matcher/ios-uikit.md +1 -1
- package/skills/massa-ai/references/mobile-figma-matcher/kmp-compose-multiplatform.md +1 -1
- package/skills/massa-ai/references/mobile-figma-matcher/repository-detection.md +1 -1
- package/skills/massa-ai/references/naming-standards.md +1 -1
- package/skills/massa-ai/references/pr-task-fix.md +2 -2
- package/skills/massa-ai/references/project-context.md +2 -2
- package/skills/massa-ai/references/repo-rules-discovery.md +4 -4
- package/skills/massa-ai/references/rfc/discovery-and-sizing.md +1 -1
- package/skills/massa-ai/references/rfc/document-contract.md +1 -1
- package/skills/massa-ai/references/rfc/quality-and-lifecycle.md +1 -1
- package/skills/massa-ai/references/root-cause-scripts.md +2 -2
- package/skills/massa-ai/references/skill-architect/examples.md +256 -0
- package/skills/massa-ai/references/skill-architect/patterns.md +317 -0
- package/skills/massa-ai/references/skill-architect/quality-checklist.md +70 -0
- package/skills/massa-ai/references/sonarqube-mcp.md +73 -0
- package/skills/massa-ai/references/spec-driven/artifact-store.md +1 -1
- package/skills/massa-ai/references/spec-driven/brownfield-mapping.md +16 -0
- package/skills/massa-ai/references/spec-driven/code-analysis.md +1 -1
- package/skills/massa-ai/references/spec-driven/coding-principles.md +1 -1
- package/skills/massa-ai/references/spec-driven/context-limits.md +1 -1
- package/skills/massa-ai/references/spec-driven/design.md +22 -1
- package/skills/massa-ai/references/spec-driven/discuss.md +1 -1
- package/skills/massa-ai/references/spec-driven/execute.md +3 -1
- package/skills/massa-ai/references/spec-driven/memory.md +1 -1
- package/skills/massa-ai/references/spec-driven/specify.md +3 -3
- package/skills/massa-ai/references/spec-driven/sub-agents.md +1 -1
- package/skills/massa-ai/references/spec-driven/tasks.md +1 -1
- package/skills/massa-ai/references/spec-driven/validate.md +1 -1
- package/skills/massa-ai/references/subagent-design.md +4 -4
- package/skills/massa-ai/references/synapse-policy.md +1 -1
- package/skills/massa-ai/references/tdd/calibrated-examples.md +1 -1
- package/skills/massa-ai/references/tdd/discovery-and-sizing.md +1 -1
- package/skills/massa-ai/references/tdd/document-contract.md +1 -1
- package/skills/massa-ai/references/tdd/quality-and-lifecycle.md +1 -1
- package/skills/massa-ai/references/the-fool/cognitive-bias-inventory.md +1 -1
- package/skills/massa-ai/references/the-fool/dialectic-synthesis.md +1 -1
- package/skills/massa-ai/references/the-fool/evidence-audit.md +1 -1
- package/skills/massa-ai/references/the-fool/pre-mortem-analysis.md +1 -1
- package/skills/massa-ai/references/the-fool/red-team-adversarial.md +1 -1
- package/skills/massa-ai/references/the-fool/socratic-questioning.md +1 -1
- package/skills/massa-ai/references/ticket/atlassian-fix.md +1 -1
- package/skills/massa-ai/references/ticket/intake-and-sources.md +1 -1
- package/skills/massa-ai/references/ticket/templates-and-quality.md +1 -1
- package/skills/massa-ai/references/verification-ladder.md +1 -1
- package/skills/massa-ai/scripts/validate_audit_report.ts +382 -0
- package/skills/massa-ai/scripts/validate_design.ts +264 -0
- package/skills/massa-ai/scripts/validate_skill.ts +364 -0
- package/skills/massa-ai/workflows/adr.md +16 -8
- package/skills/massa-ai/workflows/architecture/architecture-audit.md +23 -40
- package/skills/massa-ai/workflows/architecture/architecture-fix.md +14 -6
- package/skills/massa-ai/workflows/bugs/bugs-audit.md +19 -35
- package/skills/massa-ai/workflows/bugs/bugs-fix.md +13 -5
- package/skills/massa-ai/workflows/code-quality/code-quality-audit.md +25 -41
- package/skills/massa-ai/workflows/code-quality/code-quality-fix.md +13 -5
- package/skills/massa-ai/workflows/commit.md +13 -5
- package/skills/massa-ai/workflows/debug.md +11 -3
- package/skills/massa-ai/workflows/design.md +15 -7
- package/skills/massa-ai/workflows/exploration.md +12 -4
- package/skills/massa-ai/workflows/feature.md +14 -13
- package/skills/massa-ai/workflows/general.md +13 -8
- package/skills/massa-ai/workflows/implementation/implementation-audit.md +15 -15
- package/skills/massa-ai/workflows/implementation/implementation-fix.md +13 -5
- package/skills/massa-ai/workflows/judge-with-debate.md +12 -4
- package/skills/massa-ai/workflows/long-session.md +10 -2
- package/skills/massa-ai/workflows/maestro/maestro-audit.md +11 -3
- package/skills/massa-ai/workflows/maestro/maestro-fix.md +12 -4
- package/skills/massa-ai/workflows/maestro/maestro.md +12 -4
- package/skills/massa-ai/workflows/mobile-figma/mobile-figma-audit.md +11 -3
- package/skills/massa-ai/workflows/mobile-figma/mobile-figma-fix.md +12 -4
- package/skills/massa-ai/workflows/onboarding.md +10 -2
- package/skills/massa-ai/workflows/refactor.md +12 -4
- package/skills/massa-ai/workflows/refinement/furps-refinement.md +12 -4
- package/skills/massa-ai/workflows/requirements/requirements-audit.md +19 -36
- package/skills/massa-ai/workflows/requirements/requirements-fix.md +13 -5
- package/skills/massa-ai/workflows/rfc.md +10 -2
- package/skills/massa-ai/workflows/security/security-audit.md +19 -35
- package/skills/massa-ai/workflows/security/security-fix.md +13 -5
- package/skills/massa-ai/workflows/skill-architect.md +393 -0
- package/skills/massa-ai/workflows/spec-driven.md +20 -23
- package/skills/massa-ai/workflows/tdd.md +10 -2
- package/skills/massa-ai/workflows/tests/tests-audit.md +19 -35
- package/skills/massa-ai/workflows/tests/tests-fix.md +13 -5
- package/skills/massa-ai/workflows/the-fool.md +11 -3
- package/skills/massa-ai/workflows/ticket.md +10 -2
- package/skills/massa-ai/workflows/to-prd.md +81 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Synapse Policy
|
|
2
2
|
|
|
3
|
-
Load
|
|
3
|
+
Load when a task is expected to issue more than one
|
|
4
4
|
`search`, when parallel agents need isolated retrieval context, or when
|
|
5
5
|
Synapse compatibility/fallback behavior matters.
|
|
6
6
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The values below are starting reference values, not requirements. They are calibration anchors, not mandates. Treat every number as a starting-point reference to be confirmed against the project's own SLOs, load profile, and regulatory scope; override per project and record the override in the TDD. None of these tables restore a prescriptive count schema or a fixed section-count mandate.
|
|
4
4
|
|
|
5
|
-
Load
|
|
5
|
+
Load only when the TDD's conditional concerns (rollback, rollout, latency, compliance) apply and the team needs a concrete starting point. It complements `references/tdd/document-contract.md` (which owns the Conditional Concerns trigger table) by giving example budgets; it does not replace project-verified SLOs or legal obligations.
|
|
6
6
|
|
|
7
7
|
## Rollback-Trigger Table
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TDD Discovery And Sizing
|
|
2
2
|
|
|
3
|
-
Load
|
|
3
|
+
Load before source investigation, document sizing, or clarification questions for the TDD workflow.
|
|
4
4
|
|
|
5
5
|
## Evidence Order
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TDD Document Contract
|
|
2
2
|
|
|
3
|
-
Load
|
|
3
|
+
Load when drafting or revising a TDD. Use the smallest set of sections that makes the design decision-complete; headings may be renamed to match the user's language and project conventions.
|
|
4
4
|
|
|
5
5
|
## Core Sections
|
|
6
6
|
|
|
@@ -4,7 +4,7 @@ Structured bias detection for use during every challenge pass. Integrates findin
|
|
|
4
4
|
|
|
5
5
|
## Core Principle
|
|
6
6
|
|
|
7
|
-
Cognitive biases are not accusations — they are patterns in human reasoning that systematically distort judgment. The Fool's job is to flag when a bias may be influencing a decision, not to shame the user. Frame bias findings as: "This pattern is common in this type of decision, and here's how it might
|
|
7
|
+
Cognitive biases are not accusations — they are patterns in human reasoning that systematically distort judgment. The Fool's job is to flag when a bias may be influencing a decision, not to shame the user. Frame bias findings as: "This pattern is common in this type of decision, and here's how it might affect your reasoning."
|
|
8
8
|
|
|
9
9
|
## When to Use This File
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ Hegelian dialectic with steel manning for constructing the strongest possible co
|
|
|
4
4
|
|
|
5
5
|
## Core Principle
|
|
6
6
|
|
|
7
|
-
The dialectic is not about winning
|
|
7
|
+
The dialectic is not about winning — it is about producing a stronger position than either thesis or antithesis alone. The Fool's job is to argue the other side so well that the user is forced to either refine their position or acknowledge a genuine trade-off.
|
|
8
8
|
|
|
9
9
|
Key distinction: steel manning is epistemic (genuinely trying to find out if you're wrong), devil's advocacy is role-based (assigned to argue against). Apply both: steel man first, then construct the antithesis.
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ Falsificationism and evidence quality assessment for auditing whether claims are
|
|
|
4
4
|
|
|
5
5
|
## Core Principle
|
|
6
6
|
|
|
7
|
-
Karl Popper's key insight: a claim is only meaningful if you can specify what would disprove it. The Evidence Audit mode extracts claims from proposals, designs falsification criteria, assesses evidence quality, identifies cognitive biases, and surfaces competing explanations. The goal is not to disprove — it is to determine whether the evidence
|
|
7
|
+
Karl Popper's key insight: a claim is only meaningful if you can specify what would disprove it. The Evidence Audit mode extracts claims from proposals, designs falsification criteria, assesses evidence quality, identifies cognitive biases, and surfaces competing explanations. The goal is not to disprove — it is to determine whether the evidence supports the conclusion.
|
|
8
8
|
|
|
9
9
|
## Process
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ Pre-mortem methodology (Gary Klein) with second-order thinking for identifying h
|
|
|
4
4
|
|
|
5
5
|
## Core Principle
|
|
6
6
|
|
|
7
|
-
A pre-mortem inverts the question. Instead of "Will this work?" ask: **"It's 6 months from now and this has failed. Why?"** This psychological shift bypasses optimism bias by making failure the starting point, not the thing
|
|
7
|
+
A pre-mortem inverts the question. Instead of "Will this work?" ask: **"It's 6 months from now and this has failed. Why?"** This psychological shift bypasses optimism bias by making failure the starting point, not the thing being argued against.
|
|
8
8
|
|
|
9
9
|
Research shows prospective hindsight increases correct failure identification by 30% compared to asking "what could go wrong?" directly.
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ Adversarial thinking and red teaming for finding weaknesses before adversaries d
|
|
|
4
4
|
|
|
5
5
|
## Core Principle
|
|
6
6
|
|
|
7
|
-
Red teaming asks: **"If someone wanted to break, exploit, or game this, how would they do it?"** The Fool adopts the mindset of an adversary — not to cause harm, but to find vulnerabilities before real adversaries do. This applies beyond security: competitors, disgruntled users, perverse incentives, and regulatory challenges are all adversarial forces.
|
|
7
|
+
Red teaming asks: **"If someone wanted to break, exploit, or game this, how would they do it?"** The Fool adopts the mindset of an adversary — not to cause harm, but to find vulnerabilities before real adversaries do. This applies beyond security: competitors, disgruntled users, perverse incentives, and regulatory challenges are all adversarial forces too.
|
|
8
8
|
|
|
9
9
|
## The RED Model
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ Structured question frameworks for exposing assumptions and deepening understand
|
|
|
4
4
|
|
|
5
5
|
## Core Principle
|
|
6
6
|
|
|
7
|
-
Socratic questioning does not argue
|
|
7
|
+
Socratic questioning does not argue — it asks. The goal is to help the user discover gaps in their own reasoning by surfacing what they have not examined. Every question should create a moment of "I hadn't thought about that."
|
|
8
8
|
|
|
9
9
|
The agent must never answer the questions itself. Present them, let the user sit with them.
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ticket Intake And Sources
|
|
2
2
|
|
|
3
|
-
Use
|
|
3
|
+
Use from `workflows/ticket.md` before drafting. Gather only missing decisions, keep source roles explicit, and avoid repository-derived ticket conventions.
|
|
4
4
|
|
|
5
5
|
## Ordered Intake
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ticket Templates And Quality
|
|
2
2
|
|
|
3
|
-
Use
|
|
3
|
+
Use when decomposing work, selecting prefixes, drafting descriptions, and validating the review artifact.
|
|
4
4
|
|
|
5
5
|
## Title Prefixes
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Verification Ladder
|
|
2
2
|
|
|
3
|
-
Use
|
|
3
|
+
Use before completing work, and before Quick/Standard/Spec-driven sizing, shared-reference loading, or a verification recipe.
|
|
4
4
|
|
|
5
5
|
## Task Sizing Gate
|
|
6
6
|
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* validate_audit_report.ts - deterministic schema/ID checks for a saved
|
|
4
|
+
* audit report markdown file (STO-9 top validator pack).
|
|
5
|
+
*
|
|
6
|
+
* Replaces the inline "reread the report and eyeball the finding IDs"
|
|
7
|
+
* checklist prose that used to live in `references/audit-report-io.md` and
|
|
8
|
+
* the 9 `*-fix.md` workflows with a scripted check, parameterized by report
|
|
9
|
+
* family:
|
|
10
|
+
*
|
|
11
|
+
* - metadata field presence (common freshness header + per-family extras)
|
|
12
|
+
* - PREFIX-N finding-ID format (or `<Area>/<PREFIX>-N` for the
|
|
13
|
+
* implementation composite family)
|
|
14
|
+
* - Area<->Prefix table membership (implementation family only) - rejects
|
|
15
|
+
* unrecognized Areas, mismatched Area/Prefix pairs, and raw `SONAR-*`
|
|
16
|
+
* executable IDs
|
|
17
|
+
* - finding-ID uniqueness within the report
|
|
18
|
+
* - gap-free per-prefix (or per-area) sequencing: N, N+1, N+2, ... with no
|
|
19
|
+
* skipped numbers
|
|
20
|
+
*
|
|
21
|
+
* Operates only on the saved report markdown - never on the target
|
|
22
|
+
* codebase - so it stays stack-agnostic. Bun builtins only, zero
|
|
23
|
+
* dependencies, same heuristic-markdown-inspection style as validate_spec.ts.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* bun skills/massa-ai/scripts/validate_audit_report.ts <report.md> [--family NAME] [--strict]
|
|
27
|
+
*
|
|
28
|
+
* report.md Path to a saved audit report markdown file.
|
|
29
|
+
* --family One of: architecture | bugs | code-quality | security |
|
|
30
|
+
* requirements | tests | maestro | mobile-figma | implementation
|
|
31
|
+
* Auto-detected from the report's `Workflow:` metadata line
|
|
32
|
+
* when omitted.
|
|
33
|
+
* --strict Treat warnings as errors.
|
|
34
|
+
*
|
|
35
|
+
* Exit codes: 0 pass, 1 errors found (or warnings under --strict), 2 usage error.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
39
|
+
|
|
40
|
+
interface FamilyConfig {
|
|
41
|
+
workflow: string;
|
|
42
|
+
/** Single-lens/MFM/MST prefix, or null for the implementation composite family. */
|
|
43
|
+
prefix: string | null;
|
|
44
|
+
extraMetadata: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const COMMON_METADATA = [
|
|
48
|
+
"Date",
|
|
49
|
+
"Workflow",
|
|
50
|
+
"ProjectId",
|
|
51
|
+
"WorkflowSessionId",
|
|
52
|
+
"Target",
|
|
53
|
+
"Target Focus",
|
|
54
|
+
"Scope",
|
|
55
|
+
"Git Base",
|
|
56
|
+
"Git Head",
|
|
57
|
+
"Source Evidence Timestamp",
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** Canonical area/prefix pairs (audit-report-io.md, Source-Qualified Finding IDs). */
|
|
61
|
+
const AREA_PREFIX: Array<{ area: string; prefix: string }> = [
|
|
62
|
+
{ area: "Correctness", prefix: "BUG" },
|
|
63
|
+
{ area: "Architecture", prefix: "ARCH" },
|
|
64
|
+
{ area: "Code Quality", prefix: "CQ" },
|
|
65
|
+
{ area: "Security", prefix: "SEC" },
|
|
66
|
+
{ area: "Requirements", prefix: "REQ" },
|
|
67
|
+
{ area: "Tests", prefix: "TST" },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const FAMILIES: Record<string, FamilyConfig> = {
|
|
71
|
+
architecture: { workflow: "architecture-audit", prefix: "ARCH", extraMetadata: ["Requirements Source"] },
|
|
72
|
+
bugs: { workflow: "bugs-audit", prefix: "BUG", extraMetadata: ["Requirements Source"] },
|
|
73
|
+
"code-quality": { workflow: "code-quality-audit", prefix: "CQ", extraMetadata: ["Requirements Source"] },
|
|
74
|
+
security: { workflow: "security-audit", prefix: "SEC", extraMetadata: ["Requirements Source"] },
|
|
75
|
+
requirements: { workflow: "requirements-audit", prefix: "REQ", extraMetadata: ["Requirements Source"] },
|
|
76
|
+
tests: { workflow: "tests-audit", prefix: "TST", extraMetadata: ["Requirements Source"] },
|
|
77
|
+
maestro: {
|
|
78
|
+
workflow: "maestro-audit",
|
|
79
|
+
prefix: "MST",
|
|
80
|
+
extraMetadata: ["Scenario Source", "Maestro CLI", "Device/Emulator Readiness"],
|
|
81
|
+
},
|
|
82
|
+
"mobile-figma": {
|
|
83
|
+
workflow: "mobile-figma-audit",
|
|
84
|
+
prefix: "MFM",
|
|
85
|
+
extraMetadata: ["Repository Classification", "Figma Source", "Figma Evidence Timestamp", "Requirements Source"],
|
|
86
|
+
},
|
|
87
|
+
implementation: { workflow: "implementation-audit", prefix: null, extraMetadata: ["Requirements Source"] },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Mirrors Python's str.splitlines(): universal newline split, no trailing empty element. */
|
|
91
|
+
function splitLines(text: string): string[] {
|
|
92
|
+
if (text === "") return [];
|
|
93
|
+
const result: string[] = [];
|
|
94
|
+
const lineBreakRe = /\r\n|\r|\n/g;
|
|
95
|
+
let start = 0;
|
|
96
|
+
let match: RegExpExecArray | null;
|
|
97
|
+
while ((match = lineBreakRe.exec(text)) !== null) {
|
|
98
|
+
result.push(text.slice(start, match.index));
|
|
99
|
+
start = match.index + match[0].length;
|
|
100
|
+
}
|
|
101
|
+
if (start < text.length) result.push(text.slice(start));
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Metadata lines live between the `# Title` line and the first `##` heading. */
|
|
106
|
+
function metadataBounds(lines: string[]): [number, number] {
|
|
107
|
+
let start = 0;
|
|
108
|
+
for (let i = 0; i < lines.length; i++) {
|
|
109
|
+
if (/^#\s+\S/.test(lines[i]!)) {
|
|
110
|
+
start = i + 1;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let end = lines.length;
|
|
115
|
+
for (let i = start; i < lines.length; i++) {
|
|
116
|
+
if (/^##\s+\S/.test(lines[i]!)) {
|
|
117
|
+
end = i;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return [start, end];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseMetadata(lines: string[]): Map<string, string> {
|
|
125
|
+
const [start, end] = metadataBounds(lines);
|
|
126
|
+
const fields = new Map<string, string>();
|
|
127
|
+
const lineRe = /^([A-Za-z][A-Za-z /]*?):\s*(.*)$/;
|
|
128
|
+
for (let i = start; i < end; i++) {
|
|
129
|
+
const m = lineRe.exec(lines[i]!.trim());
|
|
130
|
+
if (m) fields.set(m[1]!.trim(), m[2]!.trim());
|
|
131
|
+
}
|
|
132
|
+
return fields;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const PLACEHOLDER_RE = /^<.*>$/;
|
|
136
|
+
|
|
137
|
+
function checkMetadata(fields: Map<string, string>, required: string[], errors: string[]): void {
|
|
138
|
+
for (const name of required) {
|
|
139
|
+
const value = fields.get(name);
|
|
140
|
+
if (value === undefined || value === "") {
|
|
141
|
+
errors.push(`missing required metadata field: ${name}`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (PLACEHOLDER_RE.test(value)) {
|
|
145
|
+
// "Requirements Source" and similar fields explicitly allow the
|
|
146
|
+
// literal value "n/a"; only an unfilled `<template>` token is an error.
|
|
147
|
+
errors.push(`metadata field '${name}' still has an unfilled template value: ${value}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface RawFinding {
|
|
153
|
+
raw: string;
|
|
154
|
+
area: string | null;
|
|
155
|
+
prefix: string;
|
|
156
|
+
num: number | null;
|
|
157
|
+
line: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const FINDING_HEADER_RE = /^###\s+([^:]+):/;
|
|
161
|
+
/** `<PREFIX>-<N>` (single-lens/MFM/MST) or `<Area>/<PREFIX>-<N>` (implementation composite). */
|
|
162
|
+
const SIMPLE_ID_RE = /^([A-Z][A-Z]*)-(\d+)$/;
|
|
163
|
+
const COMPOSITE_ID_RE = /^([A-Za-z][A-Za-z ]*?)\/([A-Z][A-Z]*)-(\d+)$/;
|
|
164
|
+
|
|
165
|
+
/** Every `### <id>: <title>` header inside the `## Findings` section, parsed best-effort. */
|
|
166
|
+
function parseFindings(lines: string[], composite: boolean): RawFinding[] {
|
|
167
|
+
const findings: RawFinding[] = [];
|
|
168
|
+
let inFindings = false;
|
|
169
|
+
for (let i = 0; i < lines.length; i++) {
|
|
170
|
+
const line = lines[i]!;
|
|
171
|
+
if (/^##\s+Findings\s*$/.test(line.trim())) {
|
|
172
|
+
inFindings = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (inFindings && /^##\s+\S/.test(line)) {
|
|
176
|
+
inFindings = false;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (!inFindings) continue;
|
|
180
|
+
const m = FINDING_HEADER_RE.exec(line.trim());
|
|
181
|
+
if (!m) continue;
|
|
182
|
+
const idText = m[1]!.trim();
|
|
183
|
+
if (composite) {
|
|
184
|
+
const cm = COMPOSITE_ID_RE.exec(idText);
|
|
185
|
+
if (cm) {
|
|
186
|
+
findings.push({ raw: idText, area: cm[1]!.trim(), prefix: cm[2]!, num: Number(cm[3]), line: i + 1 });
|
|
187
|
+
} else {
|
|
188
|
+
findings.push({ raw: idText, area: null, prefix: "", num: null, line: i + 1 });
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
const sm = SIMPLE_ID_RE.exec(idText);
|
|
192
|
+
if (sm) {
|
|
193
|
+
findings.push({ raw: idText, area: null, prefix: sm[1]!, num: Number(sm[2]), line: i + 1 });
|
|
194
|
+
} else {
|
|
195
|
+
findings.push({ raw: idText, area: null, prefix: "", num: null, line: i + 1 });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return findings;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function checkFindingIds(
|
|
203
|
+
findings: RawFinding[],
|
|
204
|
+
family: FamilyConfig,
|
|
205
|
+
composite: boolean,
|
|
206
|
+
errors: string[],
|
|
207
|
+
): void {
|
|
208
|
+
const seen = new Map<string, number>();
|
|
209
|
+
const byGroup = new Map<string, number[]>();
|
|
210
|
+
|
|
211
|
+
for (const f of findings) {
|
|
212
|
+
if (f.num === null) {
|
|
213
|
+
errors.push(`L${f.line}: finding id '${f.raw}' is not in the expected format`);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (f.prefix.startsWith("SONAR")) {
|
|
217
|
+
errors.push(`L${f.line}: raw '${f.raw}' is not an executable finding id - normalize SonarQube output to a supported source-qualified id first`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (composite) {
|
|
222
|
+
const areaEntry = AREA_PREFIX.find((a) => a.area === f.area);
|
|
223
|
+
if (!areaEntry) {
|
|
224
|
+
errors.push(`L${f.line}: '${f.area}' is not a recognized Area (expected one of ${AREA_PREFIX.map((a) => a.area).join(", ")})`);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (areaEntry.prefix !== f.prefix) {
|
|
228
|
+
errors.push(`L${f.line}: '${f.raw}' mixes Area '${f.area}' with prefix '${f.prefix}' (expected '${areaEntry.prefix}-N')`);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
} else if (family.prefix !== null && f.prefix !== family.prefix) {
|
|
232
|
+
errors.push(`L${f.line}: '${f.raw}' uses prefix '${f.prefix}', expected '${family.prefix}-N' for this report family`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const key = composite ? `${f.area}/${f.prefix}` : f.prefix;
|
|
237
|
+
const prior = seen.get(f.raw);
|
|
238
|
+
if (prior !== undefined) {
|
|
239
|
+
errors.push(`L${f.line}: duplicate finding id '${f.raw}' (first seen at L${prior})`);
|
|
240
|
+
} else {
|
|
241
|
+
seen.set(f.raw, f.line);
|
|
242
|
+
}
|
|
243
|
+
if (!byGroup.has(key)) byGroup.set(key, []);
|
|
244
|
+
byGroup.get(key)!.push(f.num);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const [key, nums] of byGroup) {
|
|
248
|
+
const sorted = [...new Set(nums)].sort((a, b) => a - b);
|
|
249
|
+
for (let expected = 1; expected <= sorted[sorted.length - 1]!; expected++) {
|
|
250
|
+
if (!sorted.includes(expected)) {
|
|
251
|
+
errors.push(`${key} finding numbering has a gap: missing ${key}-${expected} (present: ${sorted.map((n) => `${key}-${n}`).join(", ")})`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function detectFamily(fields: Map<string, string>): string | null {
|
|
258
|
+
const workflow = fields.get("Workflow");
|
|
259
|
+
if (!workflow) return null;
|
|
260
|
+
for (const [name, cfg] of Object.entries(FAMILIES)) {
|
|
261
|
+
if (cfg.workflow === workflow) return name;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
interface CheckResult {
|
|
267
|
+
errors: string[];
|
|
268
|
+
warnings: string[];
|
|
269
|
+
family: string | null;
|
|
270
|
+
findingCount: number;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function check(reportPath: string, familyArg: string | null): CheckResult {
|
|
274
|
+
const text = readFileSync(reportPath, "utf-8");
|
|
275
|
+
const lines = splitLines(text);
|
|
276
|
+
const errors: string[] = [];
|
|
277
|
+
const warnings: string[] = [];
|
|
278
|
+
|
|
279
|
+
const fields = parseMetadata(lines);
|
|
280
|
+
const family = familyArg ?? detectFamily(fields);
|
|
281
|
+
if (!family || !(family in FAMILIES)) {
|
|
282
|
+
errors.push(
|
|
283
|
+
`could not determine report family (pass --family or a recognized 'Workflow:' value); known families: ${Object.keys(FAMILIES).join(", ")}`,
|
|
284
|
+
);
|
|
285
|
+
return { errors, warnings, family: null, findingCount: 0 };
|
|
286
|
+
}
|
|
287
|
+
const cfg = FAMILIES[family]!;
|
|
288
|
+
const composite = cfg.prefix === null;
|
|
289
|
+
|
|
290
|
+
checkMetadata(fields, [...COMMON_METADATA, ...cfg.extraMetadata], errors);
|
|
291
|
+
|
|
292
|
+
const declaredWorkflow = fields.get("Workflow");
|
|
293
|
+
if (declaredWorkflow !== undefined && declaredWorkflow !== cfg.workflow) {
|
|
294
|
+
errors.push(`Workflow field is '${declaredWorkflow}', expected '${cfg.workflow}' for family '${family}'`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const findings = parseFindings(lines, composite);
|
|
298
|
+
checkFindingIds(findings, cfg, composite, errors);
|
|
299
|
+
|
|
300
|
+
return { errors, warnings, family, findingCount: findings.length };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const USAGE = "usage: validate_audit_report.ts [-h] [--family NAME] [--strict] <report.md>";
|
|
304
|
+
const HELP = `${USAGE}
|
|
305
|
+
|
|
306
|
+
Deterministic schema/ID checks for a saved audit report markdown file.
|
|
307
|
+
|
|
308
|
+
positional arguments:
|
|
309
|
+
report.md Path to a saved audit report markdown file
|
|
310
|
+
|
|
311
|
+
options:
|
|
312
|
+
-h, --help show this help message and exit
|
|
313
|
+
--family NAME One of: ${Object.keys(FAMILIES).join(", ")}
|
|
314
|
+
--strict Treat warnings as errors`;
|
|
315
|
+
|
|
316
|
+
interface Args {
|
|
317
|
+
target: string | null;
|
|
318
|
+
family: string | null;
|
|
319
|
+
strict: boolean;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function printUsageError(msg: string): void {
|
|
323
|
+
process.stderr.write(`${USAGE}\nvalidate_audit_report.ts: error: ${msg}\n`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function parseArgs(argv: string[]): Args | null {
|
|
327
|
+
let family: string | null = null;
|
|
328
|
+
let strict = false;
|
|
329
|
+
const positionals: string[] = [];
|
|
330
|
+
for (let i = 0; i < argv.length; i++) {
|
|
331
|
+
const a = argv[i]!;
|
|
332
|
+
if (a === "--family") {
|
|
333
|
+
if (i + 1 >= argv.length) {
|
|
334
|
+
printUsageError("argument --family: expected one argument");
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
family = argv[++i]!;
|
|
338
|
+
} else if (a.startsWith("--family=")) {
|
|
339
|
+
family = a.slice("--family=".length);
|
|
340
|
+
} else if (a === "--strict") {
|
|
341
|
+
strict = true;
|
|
342
|
+
} else if (a === "-h" || a === "--help") {
|
|
343
|
+
console.log(HELP);
|
|
344
|
+
process.exit(0);
|
|
345
|
+
} else if (a.startsWith("-") && a !== "-") {
|
|
346
|
+
printUsageError(`unrecognized arguments: ${a}`);
|
|
347
|
+
return null;
|
|
348
|
+
} else {
|
|
349
|
+
positionals.push(a);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (positionals.length !== 1) {
|
|
353
|
+
printUsageError("exactly one report.md path is required");
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
return { target: positionals[0]!, family, strict };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function main(argv: string[]): number {
|
|
360
|
+
const args = parseArgs(argv);
|
|
361
|
+
if (args === null) return 2;
|
|
362
|
+
|
|
363
|
+
if (!args.target || !existsSync(args.target)) {
|
|
364
|
+
console.error(`validate_audit_report: report not found: ${args.target}`);
|
|
365
|
+
return 2;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const { errors, warnings, family, findingCount } = check(args.target, args.family);
|
|
369
|
+
for (const w of warnings) console.log(` WARN ${w}`);
|
|
370
|
+
for (const e of errors) console.log(` ERROR ${e}`);
|
|
371
|
+
const fail = errors.length > 0 || (warnings.length > 0 && args.strict);
|
|
372
|
+
console.log(
|
|
373
|
+
`\nvalidate_audit_report: ${errors.length} error(s), ${warnings.length} warning(s), family=${family ?? "unknown"}, findings=${findingCount} in ${args.target}`,
|
|
374
|
+
);
|
|
375
|
+
return fail ? 1 : 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (import.meta.main) {
|
|
379
|
+
process.exit(main(process.argv.slice(2)));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export { check, FAMILIES, AREA_PREFIX };
|