program-context-protocol 0.12.4__py3-none-any.whl
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.
- pcp/__init__.py +3 -0
- pcp/assertions.py +152 -0
- pcp/attest.py +111 -0
- pcp/build_loop_bypass.py +76 -0
- pcp/build_report.py +54 -0
- pcp/capture.py +339 -0
- pcp/cli.py +104 -0
- pcp/commands/__init__.py +0 -0
- pcp/commands/amend.py +283 -0
- pcp/commands/architect_review.py +291 -0
- pcp/commands/architecture_justification.py +164 -0
- pcp/commands/audit.py +371 -0
- pcp/commands/build.py +4523 -0
- pcp/commands/build_plan.py +153 -0
- pcp/commands/build_status.py +83 -0
- pcp/commands/capture.py +72 -0
- pcp/commands/check.py +584 -0
- pcp/commands/context.py +151 -0
- pcp/commands/control_audit_cmd.py +54 -0
- pcp/commands/correct_objective.py +160 -0
- pcp/commands/dashboard.py +732 -0
- pcp/commands/deploy.py +199 -0
- pcp/commands/deploy_check.py +134 -0
- pcp/commands/design_audit.py +323 -0
- pcp/commands/diff.py +153 -0
- pcp/commands/diff_reduce.py +355 -0
- pcp/commands/docs.py +538 -0
- pcp/commands/doctor.py +820 -0
- pcp/commands/escalations_cmd.py +64 -0
- pcp/commands/gate.py +209 -0
- pcp/commands/import_project.py +404 -0
- pcp/commands/init.py +1634 -0
- pcp/commands/install_hook.py +283 -0
- pcp/commands/install_skill.py +48 -0
- pcp/commands/kickoff.py +772 -0
- pcp/commands/narrative_lint.py +54 -0
- pcp/commands/objective_conflicts_cmd.py +68 -0
- pcp/commands/pm.py +504 -0
- pcp/commands/pressure_test_cmd.py +72 -0
- pcp/commands/provenance.py +313 -0
- pcp/commands/prune.py +179 -0
- pcp/commands/report.py +49 -0
- pcp/commands/run_log_cmd.py +122 -0
- pcp/commands/scan.py +346 -0
- pcp/commands/self_update.py +125 -0
- pcp/commands/status.py +180 -0
- pcp/commands/takeover.py +55 -0
- pcp/commands/telemetry_cmd.py +167 -0
- pcp/commands/validate_module.py +153 -0
- pcp/commands/validate_strategy.py +413 -0
- pcp/commands/verify.py +166 -0
- pcp/commands/verify_syntax_fix.py +74 -0
- pcp/commands/watch.py +372 -0
- pcp/config_audit.py +141 -0
- pcp/context_map.py +124 -0
- pcp/control_audit.py +159 -0
- pcp/coupling.py +178 -0
- pcp/coverage_audit.py +77 -0
- pcp/decision_log.py +134 -0
- pcp/discovery/__init__.py +0 -0
- pcp/discovery/clusters.py +124 -0
- pcp/discovery/graph.py +110 -0
- pcp/discovery/scanner.py +109 -0
- pcp/escalations.py +193 -0
- pcp/evidence.py +30 -0
- pcp/evidence_chain.py +56 -0
- pcp/impact.py +164 -0
- pcp/install_approvals.py +44 -0
- pcp/integrity_audit.py +176 -0
- pcp/librarian.py +89 -0
- pcp/llm/__init__.py +0 -0
- pcp/llm/client.py +183 -0
- pcp/llm/coding_agent_contract.py +104 -0
- pcp/llm/harness/__init__.py +12 -0
- pcp/llm/harness/agy.py +121 -0
- pcp/llm/harness/agy_coding_loop.py +180 -0
- pcp/llm/harness/claude.py +241 -0
- pcp/llm/ledger.py +47 -0
- pcp/narrative_lint.py +229 -0
- pcp/nav_graph.py +226 -0
- pcp/objective_conflicts.py +129 -0
- pcp/operational.py +70 -0
- pcp/orphaned_work.py +262 -0
- pcp/pcp_dir.py +35 -0
- pcp/pcp_status.py +313 -0
- pcp/policy.py +81 -0
- pcp/pressure_test.py +196 -0
- pcp/qa.py +445 -0
- pcp/run_log.py +225 -0
- pcp/schema/__init__.py +0 -0
- pcp/schema/ci_rules.schema.json +106 -0
- pcp/schema/controls.schema.json +39 -0
- pcp/schema/module_acceptance.schema.json +144 -0
- pcp/schema/module_spec.schema.json +78 -0
- pcp/schema/sdlc_phase.schema.json +52 -0
- pcp/schema/validator.py +77 -0
- pcp/skill_data/pcp/SKILL.md +1897 -0
- pcp/spec_write.py +269 -0
- pcp/spend.py +77 -0
- pcp/symbols.py +86 -0
- pcp/telemetry.py +308 -0
- pcp/uat.py +271 -0
- pcp/version_drift.py +222 -0
- program_context_protocol-0.12.4.dist-info/METADATA +123 -0
- program_context_protocol-0.12.4.dist-info/RECORD +109 -0
- program_context_protocol-0.12.4.dist-info/WHEEL +4 -0
- program_context_protocol-0.12.4.dist-info/entry_points.txt +2 -0
- program_context_protocol-0.12.4.dist-info/licenses/LICENSE-APACHE +202 -0
- program_context_protocol-0.12.4.dist-info/licenses/LICENSE-MIT +21 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://pcp-protocol.com/schema/ci_rules.schema.json",
|
|
4
|
+
"title": "PCP CI Rules",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "rules"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"enum": ["1.0"]
|
|
12
|
+
},
|
|
13
|
+
"retention": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"description": "Optional pruning config for `pcp prune` -- how many days of raw QA evidence (.pcp/evidence/*.txt) and session transcripts (.pcp/transcripts/*.jsonl.gz) to keep before they're eligible for deletion. Unset means pcp prune does nothing unless overridden by its own --evidence-days/--transcript-days CLI flags -- no retention happens by default, since a silent auto-delete of audit evidence would be a worse default than an unbounded-growth warning.",
|
|
17
|
+
"properties": {
|
|
18
|
+
"evidence_days": { "type": "integer", "minimum": 1 },
|
|
19
|
+
"transcript_days": { "type": "integer", "minimum": 1 }
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"ships_externally": {
|
|
23
|
+
"type": "boolean",
|
|
24
|
+
"description": "Does this project ship/distribute to anyone outside the org, or run as a network service others connect to? Copyleft (GPL/AGPL/SSPL) obligations trigger on distribution/network-service-access, not on internal-only use -- see CLAUDE.md's Prior-Art license guardrail. false = pure internal tool: GPL is fine to vendor, AGPL/SSPL still flagged for manual review regardless (network-access clause is ambiguous even for 'internal' tools reachable over a network). Unset (or true) = the existing strict MIT/Apache-style-only default applies, unchanged -- conservative by default. Human-set -- never inferred."
|
|
25
|
+
},
|
|
26
|
+
"rules": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"required": ["id", "name", "check", "severity"],
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"properties": {
|
|
33
|
+
"id": { "type": "string", "pattern": "^[A-Z]+_?[0-9]+$" },
|
|
34
|
+
"name": { "type": "string" },
|
|
35
|
+
"check": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"enum": ["ast_pattern", "file_exists", "llm_semantic", "protected_path"]
|
|
38
|
+
},
|
|
39
|
+
"pattern": { "type": "string" },
|
|
40
|
+
"target": { "type": "string" },
|
|
41
|
+
"description": { "type": "string" },
|
|
42
|
+
"message": { "type": "string" },
|
|
43
|
+
"require_present": { "type": "boolean" },
|
|
44
|
+
"severity": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"enum": ["hard_block", "advisory"]
|
|
47
|
+
},
|
|
48
|
+
"scope": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": { "type": "string" }
|
|
51
|
+
},
|
|
52
|
+
"contract": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"additionalProperties": false,
|
|
55
|
+
"description": "Optional -- Agent Behavioral Contracts reference pattern (arXiv:2602.22302), borrowed 2026-07-20 (see docs/research-rigidity-vs-reliability-2026-07.md). ABC's (P,I,G,R) shape mapped onto this schema: Governance is already `severity` above, not duplicated here. `recovery` is the one field with real behavior -- see check.py/build.py's ci_rules consumers for how it's used.",
|
|
56
|
+
"properties": {
|
|
57
|
+
"preconditions": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": { "type": "string" },
|
|
60
|
+
"description": "Conditions that must hold for this rule to even apply -- documentation only in this version, not evaluated."
|
|
61
|
+
},
|
|
62
|
+
"invariants": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": { "type": "string" },
|
|
65
|
+
"description": "Properties that must hold throughout execution, not just at the point checked -- documentation only in this version, not evaluated."
|
|
66
|
+
},
|
|
67
|
+
"recovery": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"enum": ["retry", "escalate", "quarantine", "block"],
|
|
70
|
+
"description": "What should happen when this rule fires, beyond the flat severity gate. 'escalate' additionally logs an escalation entry immediately when the rule fires (build.py), rather than waiting for 3rd-attempt exhaustion. 'retry'/'quarantine'/'block' are declared for completeness and future use; only 'escalate' changes behavior in this version -- see build.py's _apply_rule_recovery for the honest current scope."
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"allOf": [
|
|
76
|
+
{
|
|
77
|
+
"if": { "properties": { "check": { "const": "ast_pattern" } } },
|
|
78
|
+
"then": { "required": ["pattern"] }
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"if": { "properties": { "check": { "const": "file_exists" } } },
|
|
82
|
+
"then": { "required": ["target"] }
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"if": { "properties": { "check": { "const": "llm_semantic" } } },
|
|
86
|
+
"then": { "required": ["description"] }
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"if": { "properties": { "check": { "const": "protected_path" } } },
|
|
90
|
+
"then": { "required": ["scope"] }
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"if": {
|
|
94
|
+
"properties": {
|
|
95
|
+
"check": { "const": "ast_pattern" },
|
|
96
|
+
"require_present": { "const": true }
|
|
97
|
+
},
|
|
98
|
+
"required": ["require_present"]
|
|
99
|
+
},
|
|
100
|
+
"then": { "required": ["scope"] }
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://pcp-protocol.com/schema/controls.schema.json",
|
|
4
|
+
"title": "PCP Control Catalog",
|
|
5
|
+
"description": "Canonical list of gates PCP enforces, cross-referenced to NIST SP 800-218 (SSDF) practices. Human-readable jumpstart for a CMMC/SSDF-style audit document — not a certified mapping. Read by future `pcp provenance`/audit-evidence tooling; control ids here must match control_id values written to telemetry.jsonl.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "controls"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"version": { "type": "string", "enum": ["1.0"] },
|
|
11
|
+
"controls": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["id", "name", "layer", "enforcement", "description"],
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"properties": {
|
|
18
|
+
"id": { "type": "string", "pattern": "^CTRL-[0-9]+$" },
|
|
19
|
+
"name": { "type": "string" },
|
|
20
|
+
"layer": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"enum": ["layer1", "layer2", "layer3", "wave-merge", "build-loop", "cross-cutting"]
|
|
23
|
+
},
|
|
24
|
+
"mechanism": { "type": "string" },
|
|
25
|
+
"tool": { "type": "string" },
|
|
26
|
+
"enforcement": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["hard_block", "advisory", "tracked", "override"]
|
|
29
|
+
},
|
|
30
|
+
"description": { "type": "string" },
|
|
31
|
+
"ssdf_practice": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": { "type": "string" }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://pcp-protocol.com/schema/module_acceptance.schema.json",
|
|
4
|
+
"title": "PCP Module Acceptance Criteria",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["module", "criteria"],
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"module": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"pattern": "^[a-z][a-z0-9_-]*$"
|
|
15
|
+
},
|
|
16
|
+
"criteria": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"minItems": 1,
|
|
19
|
+
"items": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"required": ["id", "description", "check", "status"],
|
|
22
|
+
"additionalProperties": true,
|
|
23
|
+
"properties": {
|
|
24
|
+
"id": { "type": "string" },
|
|
25
|
+
"description": { "type": "string" },
|
|
26
|
+
"check": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["ast_pattern", "file_exists", "test_passes", "manual", "dom_contains", "url_responds", "visual"]
|
|
29
|
+
},
|
|
30
|
+
"target": { "type": "string" },
|
|
31
|
+
"pattern": { "type": "string" },
|
|
32
|
+
"depends_on": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": { "type": "string" },
|
|
35
|
+
"description": "Other criterion ids in this module this one requires to be built first. Presence of this key on ANY criterion in the module is the opt-in signal for criterion-level parallel builds (see build.py's _criteria_parallel_enabled) -- without it, criteria in a module stay strictly sequential, the pre-existing default. An empty list still counts as opt-in (deliberately declared as independent)."
|
|
36
|
+
},
|
|
37
|
+
"test": { "type": "string" },
|
|
38
|
+
"url": { "type": "string" },
|
|
39
|
+
"selector": { "type": "string" },
|
|
40
|
+
"notes": { "type": "string" },
|
|
41
|
+
"reference_image": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Path to a local screenshot/mockup/Figma export image file, relative to project root, that a PM can point a UI-facing criterion at instead of describing the screen in prose alone. Consumed by build.py's _run_visual_quality_check as a second image the VLM judge compares the rendered screenshot against (layout/structure similarity, not pixel-perfect match). A remote URL is not fetched -- must be a file already present in the repo. Optional -- absence just means that check runs without a reference."
|
|
44
|
+
},
|
|
45
|
+
"nav_depth": {
|
|
46
|
+
"type": "integer",
|
|
47
|
+
"minimum": 0,
|
|
48
|
+
"description": "Number of clicks/taps from the app's primary entry point (login/home) to reach this feature, self-declared like logic_tier/build_vs_buy -- not computed via real routing-graph analysis (that needs per-framework route parsing, a bigger build than this field earns on its own). Audited by pcp design-audit against PCP_NAV_DEPTH_THRESHOLD (default 3, the classic UX heuristic); missing on a UI-facing criterion is itself a finding, same posture as design_justification's absence."
|
|
49
|
+
},
|
|
50
|
+
"screen_archetypes": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"enum": ["dashboard", "data_entry_form", "list_table", "detail_view", "search_filter",
|
|
55
|
+
"settings", "chat", "canvas_editor", "wizard", "auth", "other"]
|
|
56
|
+
},
|
|
57
|
+
"description": "Which screen archetype(s) this criterion's UI belongs to -- a LIST, not a single value, because most real screens are composite (a detail page is often detail_view + list_table + chat all at once; see CLAUDE.md's UI Building Blocks section on why a single-enum forced the wrong shape). 'other' is the honest escape hatch for a genuine non-fit -- forcing a bad-fit archetype to satisfy a schema is worse than declaring it doesn't cleanly match one. Each archetype maps to a recipe (required ui_organisms) in .pcp/ui_kit_recipes.yaml."
|
|
58
|
+
},
|
|
59
|
+
"ui_organisms": {
|
|
60
|
+
"type": "array",
|
|
61
|
+
"items": { "type": "string" },
|
|
62
|
+
"description": "Which organism-level building blocks (data-table, primary-nav, modal, ...) this screen actually uses, self-declared by the build agent -- like logic_tier/build_vs_buy, not detected. Checked against .pcp/ui_kit_recipes.yaml's organism->component mapping by build.py's CTRL-028: a declared organism should show a matching import in the criterion's own target file, or the declaration is unverified. Also checked against the criterion's screen_archetypes recipe (are the archetype's required organisms present)."
|
|
63
|
+
},
|
|
64
|
+
"status": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"enum": ["pending", "complete", "deferred", "blocked-ci", "blocked-secret", "blocked-regression"]
|
|
67
|
+
},
|
|
68
|
+
"verified_by": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"enum": ["pcp_build", "pcp_build_install_only", "manual"],
|
|
71
|
+
"description": "Who/what flipped status to complete. Stamped automatically by build.py's _mark_criterion_complete -- the ONLY place this ever gets set by the real gated loop (full TDD/QA/architect-review cycle = pcp_build; install-only fast path, CTRL-034 = pcp_build_install_only). Absence on a complete criterion means it was hand-edited outside pcp build entirely -- never set that by hand, it would defeat the point."
|
|
72
|
+
},
|
|
73
|
+
"logic_tier": {
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"enum": [1, 2, 3, 4, 5, 6],
|
|
76
|
+
"description": "6-rung decision ladder: 1=deterministic, 2=solver, 3=ML, 4=RAG, 5=cached reuse, 6=deep-think LLM. See CLAUDE.md's Logic-Tier Selection philosophy."
|
|
77
|
+
},
|
|
78
|
+
"build_vs_buy": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"required": ["decision", "rationale"],
|
|
81
|
+
"additionalProperties": false,
|
|
82
|
+
"properties": {
|
|
83
|
+
"decision": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"enum": ["reuse_whole", "reuse_partial", "reimplement_from_reference", "fork_adapt", "build_fresh"]
|
|
86
|
+
},
|
|
87
|
+
"rationale": { "type": "string" },
|
|
88
|
+
"candidates_considered": {
|
|
89
|
+
"type": "array",
|
|
90
|
+
"items": { "type": "string" }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"install_only": {
|
|
95
|
+
"type": "boolean",
|
|
96
|
+
"description": "Set when priorart found a direct match and a human confirmed it (not an automated confidence heuristic) -- see CLAUDE.md's install-only fast path. build.py's _build_one_criterion skips the full TDD/architect-review/LLM-gate cycle for this criterion: interactive human approval (or --yes), run install_command, then a deterministic smoke test only (full test suite + Layer 1 ci_rules -- CTRL-034). Requires install_command. Declining the approval prompt, or a failed smoke test, falls through to the normal full build path -- this is never a silent skip."
|
|
97
|
+
},
|
|
98
|
+
"install_command": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Shell command that installs the direct-match dependency (e.g. 'pip install acroforge'). Required when install_only is true; ignored otherwise."
|
|
101
|
+
},
|
|
102
|
+
"design_justification": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"description": "PCP Design lifecycle, stage 2 (Decide per-screen) -- only meaningful for UI-facing criteria. Not schema-required (whether a criterion is UI-facing isn't expressible as a JSON-schema condition on free-text description) -- absence on a real UI criterion is instead a finding surfaced by pcp design-audit, not a validation failure.",
|
|
105
|
+
"additionalProperties": false,
|
|
106
|
+
"properties": {
|
|
107
|
+
"checklist_passed": {
|
|
108
|
+
"type": "array",
|
|
109
|
+
"items": { "type": "string" }
|
|
110
|
+
},
|
|
111
|
+
"deviations_from_system": { "type": "string" },
|
|
112
|
+
"jtbd_framing": { "type": "string" },
|
|
113
|
+
"customizable": {
|
|
114
|
+
"type": "boolean",
|
|
115
|
+
"description": "Does this feature expose real user-configurable settings/preferences affecting its own behavior, or is it fixed? Structurally checked (not just presence) by build.py's CTRL-026 -- a customizable=true declaration with no settings-shaped signal anywhere in the target file is flagged advisory, same posture as design_justification's own substance check."
|
|
116
|
+
},
|
|
117
|
+
"customization_notes": {
|
|
118
|
+
"type": "string",
|
|
119
|
+
"description": "What's actually configurable, one sentence. Required in spirit (not schema) whenever customizable=true -- an empty/placeholder note there is exactly what CTRL-026 flags."
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"allOf": [
|
|
128
|
+
{
|
|
129
|
+
"if": {
|
|
130
|
+
"properties": { "version": { "const": "2.0" } },
|
|
131
|
+
"required": ["version"]
|
|
132
|
+
},
|
|
133
|
+
"then": {
|
|
134
|
+
"properties": {
|
|
135
|
+
"criteria": {
|
|
136
|
+
"items": {
|
|
137
|
+
"required": ["id", "description", "check", "status", "logic_tier", "build_vs_buy"]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://pcp-protocol.com/schema/module_spec.schema.json",
|
|
4
|
+
"title": "PCP Module Spec",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["module", "description"],
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"module": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"pattern": "^[a-z][a-z0-9_-]*$"
|
|
15
|
+
},
|
|
16
|
+
"description": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"minLength": 10
|
|
19
|
+
},
|
|
20
|
+
"objective_coverage": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"items": { "type": "string" },
|
|
23
|
+
"minItems": 1
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": { "type": "string" }
|
|
28
|
+
},
|
|
29
|
+
"constraints": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": { "type": "string" }
|
|
32
|
+
},
|
|
33
|
+
"module_logic_breakdown": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"items": { "type": "string" },
|
|
36
|
+
"description": "Decompose-first, one level deeper than objective_coverage (GUIDE, arXiv:2502.21068, already applied program-wide via capabilities_enumerated): this module's internal components/sub-flows/edge-cases, declared BEFORE its acceptance criteria are generated so criteria derive from a real breakdown rather than a high-level restatement of the description. Optional, living artifact -- allowed to evolve once real implementation constraints surface, same posture design_system.md already has."
|
|
37
|
+
},
|
|
38
|
+
"deprecated": {
|
|
39
|
+
"type": "boolean"
|
|
40
|
+
},
|
|
41
|
+
"build_vs_buy": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["decision", "rationale"],
|
|
44
|
+
"additionalProperties": false,
|
|
45
|
+
"description": "Module-level build-vs-buy for infrastructure-shaped modules (portal, auth, integrations, orchestration engine). 'not_applicable' for pure business-logic modules -- see per-criterion build_vs_buy in acceptance.yaml instead.",
|
|
46
|
+
"properties": {
|
|
47
|
+
"decision": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": ["reuse_whole", "reuse_partial", "reimplement_from_reference", "fork_adapt", "build_fresh", "not_applicable"]
|
|
50
|
+
},
|
|
51
|
+
"rationale": { "type": "string" },
|
|
52
|
+
"candidates_considered": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": { "type": "string" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"install_only": {
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"description": "Whole-module direct-match fast path -- the entire module's pending criteria are satisfied by installing one existing dependency, human-confirmed (not an automated heuristic). build.py's _build_module_worker skips the full per-criterion TDD/architect-review/LLM-gate loop entirely: one interactive approval (or --yes), run install_command once, then one deterministic smoke test (full test suite + Layer 1 -- CTRL-034) covering the whole module. All pending criteria are marked complete together on success. Requires install_command. Declining the approval, or a failed smoke test, falls through to the normal per-criterion build loop -- never a silent skip."
|
|
61
|
+
},
|
|
62
|
+
"install_command": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "Shell command that installs the direct-match dependency for the whole module. Required when install_only is true; ignored otherwise."
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"allOf": [
|
|
68
|
+
{
|
|
69
|
+
"if": {
|
|
70
|
+
"properties": { "version": { "const": "2.0" } },
|
|
71
|
+
"required": ["version"]
|
|
72
|
+
},
|
|
73
|
+
"then": {
|
|
74
|
+
"required": ["build_vs_buy"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://pcp-protocol.com/schema/sdlc_phase.schema.json",
|
|
4
|
+
"title": "PCP SDLC Phase",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["version", "current_phase", "phases"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"version": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"enum": ["1.0"]
|
|
12
|
+
},
|
|
13
|
+
"current_phase": { "type": "string" },
|
|
14
|
+
"phases": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"minItems": 1,
|
|
17
|
+
"items": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"required": ["name", "exit_criteria"],
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"properties": {
|
|
22
|
+
"name": { "type": "string" },
|
|
23
|
+
"scope": {
|
|
24
|
+
"type": "array",
|
|
25
|
+
"items": { "type": "string" }
|
|
26
|
+
},
|
|
27
|
+
"exit_criteria": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"required": ["id", "description", "check", "status"],
|
|
32
|
+
"additionalProperties": false,
|
|
33
|
+
"properties": {
|
|
34
|
+
"id": { "type": "string" },
|
|
35
|
+
"description": { "type": "string" },
|
|
36
|
+
"check": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["file_exists", "ast_pattern", "manual"]
|
|
39
|
+
},
|
|
40
|
+
"target": { "type": "string" },
|
|
41
|
+
"status": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"enum": ["pending", "complete"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
pcp/schema/validator.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""YAML schema validation against PCP JSON schemas."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import jsonschema
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
SCHEMA_DIR = Path(__file__).parent
|
|
11
|
+
# Real packaging bug, found 2026-07-08: this used to be
|
|
12
|
+
# Path(__file__).parent.parent.parent.parent / "schema", assuming a repo-root
|
|
13
|
+
# schema/ dir sits four levels above validator.py -- only true in this exact
|
|
14
|
+
# dev-repo layout. A real installed package (site-packages/pcp/schema/
|
|
15
|
+
# validator.py, editable or not) has no such sibling four levels up, so
|
|
16
|
+
# every validate_file() call silently pointed at a nonexistent path for
|
|
17
|
+
# anyone using PCP as an actually-installed dependency (confirmed against
|
|
18
|
+
# Project O's own separate venv install). Schemas now live directly
|
|
19
|
+
# alongside validator.py, inside the package itself, so the path resolves
|
|
20
|
+
# correctly regardless of how or where the package is installed.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _load_schema(name: str) -> dict:
|
|
24
|
+
path = SCHEMA_DIR / f"{name}.schema.json"
|
|
25
|
+
with open(path) as f:
|
|
26
|
+
return json.load(f)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validate_file(yaml_path: Path, schema_name: str) -> list[str]:
|
|
30
|
+
"""Validate a YAML file against a named schema. Returns list of error messages."""
|
|
31
|
+
try:
|
|
32
|
+
with open(yaml_path) as f:
|
|
33
|
+
data = yaml.safe_load(f)
|
|
34
|
+
except yaml.YAMLError as e:
|
|
35
|
+
return [f"YAML parse error: {e}"]
|
|
36
|
+
|
|
37
|
+
schema = _load_schema(schema_name)
|
|
38
|
+
validator = jsonschema.Draft7Validator(schema)
|
|
39
|
+
errors = sorted(validator.iter_errors(data), key=lambda e: list(e.path))
|
|
40
|
+
|
|
41
|
+
return [
|
|
42
|
+
f"{'.'.join(str(p) for p in err.path) or 'root'}: {err.message}"
|
|
43
|
+
for err in errors
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MalformedSpecError(Exception):
|
|
48
|
+
"""A spec/acceptance file exists but is not parseable YAML."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def load_yaml(path: Path) -> Any:
|
|
52
|
+
"""Parse a `.pcp/` YAML file, failing with an actionable error.
|
|
53
|
+
|
|
54
|
+
This used to let PyYAML's ScannerError escape raw. A build agent
|
|
55
|
+
hand-edited `signer-fill-sign-flow/acceptance.yaml` on 2026-07-27, writing
|
|
56
|
+
a multi-line description as a plain (unquoted) scalar containing ": ",
|
|
57
|
+
which is invalid YAML. The resulting traceback ended a 41-dollar run that
|
|
58
|
+
had already completed two modules -- with no message naming the file, the
|
|
59
|
+
line, or what to do about it, and no chance for the other modules to
|
|
60
|
+
survive.
|
|
61
|
+
|
|
62
|
+
A malformed spec is a normal, expected condition in a system where agents
|
|
63
|
+
write files: it must read as "this file is broken, here is where" and be
|
|
64
|
+
catchable per-module, not as an interpreter stack trace.
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
with open(path) as f:
|
|
68
|
+
return yaml.safe_load(f)
|
|
69
|
+
except yaml.YAMLError as exc:
|
|
70
|
+
mark = getattr(exc, "problem_mark", None)
|
|
71
|
+
where = f" at line {mark.line + 1}, column {mark.column + 1}" if mark else ""
|
|
72
|
+
problem = getattr(exc, "problem", None) or str(exc).splitlines()[0]
|
|
73
|
+
raise MalformedSpecError(
|
|
74
|
+
f"{path} is not valid YAML{where}: {problem}. "
|
|
75
|
+
f"A common cause is a multi-line description written unquoted while "
|
|
76
|
+
f"containing ': ' -- quote the string or use a block scalar (|)."
|
|
77
|
+
) from exc
|