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.
Files changed (109) hide show
  1. pcp/__init__.py +3 -0
  2. pcp/assertions.py +152 -0
  3. pcp/attest.py +111 -0
  4. pcp/build_loop_bypass.py +76 -0
  5. pcp/build_report.py +54 -0
  6. pcp/capture.py +339 -0
  7. pcp/cli.py +104 -0
  8. pcp/commands/__init__.py +0 -0
  9. pcp/commands/amend.py +283 -0
  10. pcp/commands/architect_review.py +291 -0
  11. pcp/commands/architecture_justification.py +164 -0
  12. pcp/commands/audit.py +371 -0
  13. pcp/commands/build.py +4523 -0
  14. pcp/commands/build_plan.py +153 -0
  15. pcp/commands/build_status.py +83 -0
  16. pcp/commands/capture.py +72 -0
  17. pcp/commands/check.py +584 -0
  18. pcp/commands/context.py +151 -0
  19. pcp/commands/control_audit_cmd.py +54 -0
  20. pcp/commands/correct_objective.py +160 -0
  21. pcp/commands/dashboard.py +732 -0
  22. pcp/commands/deploy.py +199 -0
  23. pcp/commands/deploy_check.py +134 -0
  24. pcp/commands/design_audit.py +323 -0
  25. pcp/commands/diff.py +153 -0
  26. pcp/commands/diff_reduce.py +355 -0
  27. pcp/commands/docs.py +538 -0
  28. pcp/commands/doctor.py +820 -0
  29. pcp/commands/escalations_cmd.py +64 -0
  30. pcp/commands/gate.py +209 -0
  31. pcp/commands/import_project.py +404 -0
  32. pcp/commands/init.py +1634 -0
  33. pcp/commands/install_hook.py +283 -0
  34. pcp/commands/install_skill.py +48 -0
  35. pcp/commands/kickoff.py +772 -0
  36. pcp/commands/narrative_lint.py +54 -0
  37. pcp/commands/objective_conflicts_cmd.py +68 -0
  38. pcp/commands/pm.py +504 -0
  39. pcp/commands/pressure_test_cmd.py +72 -0
  40. pcp/commands/provenance.py +313 -0
  41. pcp/commands/prune.py +179 -0
  42. pcp/commands/report.py +49 -0
  43. pcp/commands/run_log_cmd.py +122 -0
  44. pcp/commands/scan.py +346 -0
  45. pcp/commands/self_update.py +125 -0
  46. pcp/commands/status.py +180 -0
  47. pcp/commands/takeover.py +55 -0
  48. pcp/commands/telemetry_cmd.py +167 -0
  49. pcp/commands/validate_module.py +153 -0
  50. pcp/commands/validate_strategy.py +413 -0
  51. pcp/commands/verify.py +166 -0
  52. pcp/commands/verify_syntax_fix.py +74 -0
  53. pcp/commands/watch.py +372 -0
  54. pcp/config_audit.py +141 -0
  55. pcp/context_map.py +124 -0
  56. pcp/control_audit.py +159 -0
  57. pcp/coupling.py +178 -0
  58. pcp/coverage_audit.py +77 -0
  59. pcp/decision_log.py +134 -0
  60. pcp/discovery/__init__.py +0 -0
  61. pcp/discovery/clusters.py +124 -0
  62. pcp/discovery/graph.py +110 -0
  63. pcp/discovery/scanner.py +109 -0
  64. pcp/escalations.py +193 -0
  65. pcp/evidence.py +30 -0
  66. pcp/evidence_chain.py +56 -0
  67. pcp/impact.py +164 -0
  68. pcp/install_approvals.py +44 -0
  69. pcp/integrity_audit.py +176 -0
  70. pcp/librarian.py +89 -0
  71. pcp/llm/__init__.py +0 -0
  72. pcp/llm/client.py +183 -0
  73. pcp/llm/coding_agent_contract.py +104 -0
  74. pcp/llm/harness/__init__.py +12 -0
  75. pcp/llm/harness/agy.py +121 -0
  76. pcp/llm/harness/agy_coding_loop.py +180 -0
  77. pcp/llm/harness/claude.py +241 -0
  78. pcp/llm/ledger.py +47 -0
  79. pcp/narrative_lint.py +229 -0
  80. pcp/nav_graph.py +226 -0
  81. pcp/objective_conflicts.py +129 -0
  82. pcp/operational.py +70 -0
  83. pcp/orphaned_work.py +262 -0
  84. pcp/pcp_dir.py +35 -0
  85. pcp/pcp_status.py +313 -0
  86. pcp/policy.py +81 -0
  87. pcp/pressure_test.py +196 -0
  88. pcp/qa.py +445 -0
  89. pcp/run_log.py +225 -0
  90. pcp/schema/__init__.py +0 -0
  91. pcp/schema/ci_rules.schema.json +106 -0
  92. pcp/schema/controls.schema.json +39 -0
  93. pcp/schema/module_acceptance.schema.json +144 -0
  94. pcp/schema/module_spec.schema.json +78 -0
  95. pcp/schema/sdlc_phase.schema.json +52 -0
  96. pcp/schema/validator.py +77 -0
  97. pcp/skill_data/pcp/SKILL.md +1897 -0
  98. pcp/spec_write.py +269 -0
  99. pcp/spend.py +77 -0
  100. pcp/symbols.py +86 -0
  101. pcp/telemetry.py +308 -0
  102. pcp/uat.py +271 -0
  103. pcp/version_drift.py +222 -0
  104. program_context_protocol-0.12.4.dist-info/METADATA +123 -0
  105. program_context_protocol-0.12.4.dist-info/RECORD +109 -0
  106. program_context_protocol-0.12.4.dist-info/WHEEL +4 -0
  107. program_context_protocol-0.12.4.dist-info/entry_points.txt +2 -0
  108. program_context_protocol-0.12.4.dist-info/licenses/LICENSE-APACHE +202 -0
  109. program_context_protocol-0.12.4.dist-info/licenses/LICENSE-MIT +21 -0
@@ -0,0 +1,283 @@
1
+ """pcp install-hook — install pcp check as a git commit-msg hook (Layer 1 gate)."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import click
7
+ from rich.console import Console
8
+
9
+ from pcp.pcp_dir import find_pcp_dir, NoPCPDir
10
+
11
+ console = Console()
12
+
13
+ COMMIT_MSG_HOOK = """\
14
+ #!/bin/sh
15
+ # PCP Layer 1 gate + commit hygiene
16
+ # Installed by: pcp install-hook
17
+ #
18
+ # Strips any Co-Authored-By trailer referencing Claude/Anthropic before the
19
+ # commit message is finalized -- defense in depth for "never attribute a
20
+ # commit to Claude": even if some other tool, session, or human adds one by
21
+ # habit, it never reaches the actual commit object. Uses perl, not sed -i,
22
+ # because sed's in-place-edit flag syntax differs between BSD (macOS) and
23
+ # GNU (Linux) sed -- perl -ni is identical on both.
24
+ perl -ni -e 'print unless /^Co-Authored-By:.*(claude|anthropic)/i' "$1"
25
+ #
26
+ # Agent-session attribution trailer (2026-07-17, incremental identity
27
+ # hardening): when the commit happens inside a PCP agent session, stamp the
28
+ # session id into the commit message as a queryable trailer. Still rooted in
29
+ # a self-declared env var — honestly NOT cryptographic identity (the IETF
30
+ # dynamic-attestation draft is the eventual target) — but it moves the
31
+ # marker from "ambient env var at gate time" to "recorded on the commit
32
+ # object itself", queryable via plain `git log --grep`.
33
+ if [ -n "$PCP_AGENT_SESSION" ]; then
34
+ if ! grep -q '^PCP-Agent-Session:' "$1"; then
35
+ printf '\nPCP-Agent-Session: %s\n' "${PCP_AGENT_SESSION_ID:-unidentified}" >> "$1"
36
+ fi
37
+ fi
38
+ #
39
+ # Runs as a commit-msg hook, not pre-commit: git does not write the final
40
+ # commit message to disk until after pre-commit runs (confirmed empirically —
41
+ # COMMIT_EDITMSG holds the PREVIOUS commit's message at pre-commit time when
42
+ # committing via `-m`), so a pre-commit hook can never see a `[pcp-bypass:
43
+ # reason]` marker in the message being created. commit-msg fires after the
44
+ # message is finalized but still before the commit object is created, so it
45
+ # blocks just as effectively and the bypass marker actually works.
46
+ pcp check --commit-msg-file "$1"
47
+ """
48
+
49
+ POST_COMMIT_HOOK = """\
50
+ #!/bin/sh
51
+ # PCP post-commit state refresh
52
+ # Installed by: pcp install-hook / pcp init
53
+ #
54
+ # Regenerates current_state.md + diff.md after every commit -- not just
55
+ # when a human remembers to run `pcp scan` manually, and not just for
56
+ # commits made inside `pcp build`'s own loop (which already calls scan
57
+ # directly). Closes the gap where a commit made outside that loop (a human
58
+ # editing code directly, or `pcp pm`) left drift state stale until the next
59
+ # unrelated `pcp scan` invocation.
60
+ #
61
+ # Best-effort and silent: a post-commit hook runs after the commit object
62
+ # already exists, so it can never block or undo the commit -- failures here
63
+ # are logged, never fatal. No-ops cleanly if `pcp` isn't on PATH yet or the
64
+ # project has no modules scaffolded (pcp scan already handles both cases).
65
+ command -v pcp >/dev/null 2>&1 && pcp scan --quiet >/dev/null 2>&1
66
+ exit 0
67
+ """
68
+
69
+ PRE_COMMIT_FRAMEWORK_CONFIG = """\
70
+ repos:
71
+ - repo: local
72
+ hooks:
73
+ - id: pcp-check
74
+ name: PCP Layer 1 gate
75
+ entry: pcp check --commit-msg-file
76
+ language: system
77
+ stages: [commit-msg]
78
+ pass_filenames: true
79
+ always_run: true
80
+ """
81
+
82
+
83
+ def _find_git_dir(project_root: Path) -> Path | None:
84
+ import subprocess
85
+ try:
86
+ r = subprocess.run(["git", "rev-parse", "--git-dir"],
87
+ capture_output=True, text=True, cwd=project_root)
88
+ git_dir = Path(r.stdout.strip()) if r.returncode == 0 else None
89
+ except FileNotFoundError:
90
+ git_dir = None
91
+ if git_dir and not git_dir.is_absolute():
92
+ git_dir = project_root / git_dir
93
+ return git_dir
94
+
95
+
96
+ def install_git_hook(project_root: Path, force: bool = False) -> tuple[bool, str]:
97
+ """Just the commit-msg hook file. Pulled out of the CLI command so `pcp
98
+ init` can call this directly and get a project under real Layer-1
99
+ enforcement the moment it's scaffolded.
100
+
101
+ Returns (installed: bool, message: str) -- never raises, so callers
102
+ that want this to be a best-effort side effect (like init.py) can just
103
+ print the message and move on rather than handling exceptions."""
104
+ git_dir = _find_git_dir(project_root)
105
+ if not git_dir:
106
+ return False, "not a git repository yet -- skipped"
107
+
108
+ hooks_dir = git_dir / "hooks"
109
+ hooks_dir.mkdir(exist_ok=True)
110
+ hook_path = hooks_dir / "commit-msg"
111
+
112
+ if hook_path.exists() and not force:
113
+ if "pcp check --commit-msg-file" in hook_path.read_text():
114
+ commit_msg_result = True, f"already installed at {hook_path}"
115
+ else:
116
+ return False, f"a different commit-msg hook already exists at {hook_path} -- run `pcp install-hook --force` to overwrite, or `--pre-commit-framework` to append"
117
+ else:
118
+ hook_path.write_text(COMMIT_MSG_HOOK)
119
+ hook_path.chmod(0o755)
120
+ commit_msg_result = True, f"installed {hook_path}"
121
+
122
+ post_commit_path = hooks_dir / "post-commit"
123
+ if post_commit_path.exists() and not force:
124
+ if "pcp scan" not in post_commit_path.read_text():
125
+ # Don't clobber a human's own post-commit hook -- just skip ours.
126
+ return commit_msg_result
127
+ else:
128
+ post_commit_path.write_text(POST_COMMIT_HOOK)
129
+ post_commit_path.chmod(0o755)
130
+
131
+ return commit_msg_result
132
+
133
+
134
+ @click.command()
135
+ @click.option("--path", "project_path", type=click.Path(), default=None)
136
+ @click.option("--pre-commit-framework", is_flag=True,
137
+ help="Add to .pre-commit-config.yaml instead of .git/hooks/.")
138
+ @click.option("--force", is_flag=True, help="Overwrite existing hook.")
139
+ def install_hook(project_path: str | None, pre_commit_framework: bool, force: bool):
140
+ """Install pcp check as a git commit-msg hook (needed so `[pcp-bypass: reason]` is visible)."""
141
+ try:
142
+ pcp_dir = find_pcp_dir(Path(project_path) if project_path else None)
143
+ except NoPCPDir as e:
144
+ console.print(f"[red]Error:[/red] {e}")
145
+ sys.exit(2)
146
+
147
+ project_root = pcp_dir.parent
148
+
149
+ if pre_commit_framework:
150
+ config_path = project_root / ".pre-commit-config.yaml"
151
+ if config_path.exists():
152
+ existing = config_path.read_text()
153
+ if "pcp-check" in existing:
154
+ console.print("[dim]pcp-check already in .pre-commit-config.yaml[/dim]")
155
+ sys.exit(0)
156
+ with open(config_path, "a") as f:
157
+ f.write("\n" + PRE_COMMIT_FRAMEWORK_CONFIG)
158
+ console.print("[green]appended[/green] pcp-check to .pre-commit-config.yaml")
159
+ else:
160
+ config_path.write_text(PRE_COMMIT_FRAMEWORK_CONFIG)
161
+ console.print("[green]created[/green] .pre-commit-config.yaml with pcp-check")
162
+ return
163
+
164
+ git_dir = _find_git_dir(project_root)
165
+ if not git_dir:
166
+ console.print("[red]Error:[/red] not a git repository.")
167
+ sys.exit(2)
168
+
169
+ hooks_dir = git_dir / "hooks"
170
+ hooks_dir.mkdir(exist_ok=True)
171
+ hook_path = hooks_dir / "commit-msg"
172
+
173
+ # Bug fixed 2026-07-21: this used to sys.exit(1) here whenever commit-msg
174
+ # already existed, which meant the post-commit check below could NEVER
175
+ # run in that case -- a project with a working commit-msg hook but a
176
+ # missing post-commit hook (e.g. this repo itself, predating the
177
+ # post-commit feature) could never get it installed via this command,
178
+ # only via `pcp init`'s own install_git_hook() call, which didn't have
179
+ # this bug. commit-msg's own outcome is now independent of post-commit's.
180
+ commit_msg_installed = False
181
+ if hook_path.exists() and not force:
182
+ console.print(f"[yellow]Hook already exists:[/yellow] {hook_path}")
183
+ console.print("Use --force to overwrite (this replaces the whole file — merge manually "
184
+ "if you have other commit-msg hooks), or --pre-commit-framework to append.")
185
+ else:
186
+ hook_path.write_text(COMMIT_MSG_HOOK)
187
+ hook_path.chmod(0o755)
188
+ console.print(f"[green]installed[/green] {hook_path}")
189
+ console.print("[dim]pcp check will run before every commit finalizes.[/dim]")
190
+ commit_msg_installed = True
191
+
192
+ post_commit_path = hooks_dir / "post-commit"
193
+ if post_commit_path.exists() and not force and "pcp scan" not in post_commit_path.read_text():
194
+ console.print(f"[yellow]A different post-commit hook already exists at {post_commit_path} — skipped (use --force to overwrite).[/yellow]")
195
+ else:
196
+ post_commit_path.write_text(POST_COMMIT_HOOK)
197
+ post_commit_path.chmod(0o755)
198
+ console.print(f"[green]installed[/green] {post_commit_path}")
199
+ console.print("[dim]current_state.md + diff.md refresh after every commit.[/dim]")
200
+
201
+ _remove_legacy_cron_jobs()
202
+
203
+ # commit-msg is the primary hook this command exists for -- preserve the
204
+ # original exit-1-on-refusal contract for it specifically, even though
205
+ # post-commit (checked above, independently) may have installed fine.
206
+ if not commit_msg_installed:
207
+ sys.exit(1)
208
+
209
+
210
+ # Removed 2026-07-27, pre-launch review. This command used to also install two
211
+ # global crontab jobs via `_install_cron_scripts()`, unconditionally and with no
212
+ # prompt:
213
+ #
214
+ # 1. `upgrade_skill.sh` — daily `curl` of SKILL.md from a hardcoded personal
215
+ # GitHub raw URL, overwriting ~/.claude/skills/pcp/SKILL.md with no
216
+ # signature or hash check. That file is an AGENT INSTRUCTION file: whatever
217
+ # the fetched content says, the next `/pcp` session executes. A remote,
218
+ # unverified, self-updating instruction channel is the single worst thing
219
+ # to ship in a tool whose entire purpose is governing what agents do. It
220
+ # was dormant only because the origin repo is private (`curl -sf` on a
221
+ # private URL returns empty, so the upgrader no-opped) -- making the repo
222
+ # PUBLIC is what would have armed it.
223
+ # 2. `aggregate_interventions.sh` — scanned a hardcoded `~/Claude-code` across
224
+ # every project on the machine and posted a summary to a hardcoded Slack
225
+ # channel. Personal-workflow plumbing with no place in a public tool, and
226
+ # it interpolated each discovered path straight into a `python3 -c` string,
227
+ # so a directory name containing a quote executed arbitrary code daily.
228
+ #
229
+ # Neither was ever a documented feature of `pcp install-hook`, whose stated job
230
+ # is installing git hooks. Deleted outright rather than fixed: skill updates
231
+ # belong in the package (`pcp install-skill`, versioned via PyPI), not in an
232
+ # out-of-band self-updater.
233
+ _LEGACY_CRON_MARKER = "/.pcp/cron/"
234
+
235
+
236
+ def _remove_legacy_cron_jobs() -> None:
237
+ """Uninstall the cron jobs older PCP versions installed silently.
238
+
239
+ Deleting the installer does nothing for machines that already ran it --
240
+ those crontab entries keep firing forever. `pcp install-hook` is the
241
+ command that put them there, so it is the right place to take them back
242
+ out. Removes only lines this tool wrote (matched on the `~/.pcp/cron/`
243
+ path it always used) and reports what went; never touches any other
244
+ crontab line, and never fails the hook install if crontab is unavailable."""
245
+ import subprocess
246
+
247
+ try:
248
+ existing = subprocess.run(["crontab", "-l"], capture_output=True, text=True)
249
+ except FileNotFoundError:
250
+ return
251
+ if existing.returncode != 0:
252
+ return
253
+
254
+ lines = existing.stdout.splitlines()
255
+ keep = [ln for ln in lines if _LEGACY_CRON_MARKER not in ln]
256
+ if len(keep) == len(lines):
257
+ return
258
+
259
+ removed = [ln for ln in lines if _LEGACY_CRON_MARKER in ln]
260
+ body = "\n".join(keep).strip("\n")
261
+ proc = subprocess.run(
262
+ ["crontab", "-"], input=(body + "\n") if body else "", text=True,
263
+ capture_output=True,
264
+ )
265
+ if proc.returncode != 0:
266
+ console.print(
267
+ "[yellow]Found legacy PCP cron jobs but could not remove them "
268
+ "automatically. Remove these lines with `crontab -e`:[/yellow]"
269
+ )
270
+ for ln in removed:
271
+ console.print(f" [dim]{ln}[/dim]")
272
+ return
273
+
274
+ console.print(f"[green]removed[/green] {len(removed)} legacy PCP cron job(s):")
275
+ for ln in removed:
276
+ console.print(f" [dim]{ln}[/dim]")
277
+ console.print(
278
+ "[dim]These were installed by an older `pcp install-hook` and included a "
279
+ "daily unverified remote overwrite of your PCP skill file. The scripts "
280
+ "themselves are still on disk at ~/.pcp/cron/ — delete them yourself.[/dim]"
281
+ )
282
+
283
+
@@ -0,0 +1,48 @@
1
+ """pcp install-skill — install the full `/pcp` orchestrator skill.
2
+
3
+ Distinct from this repo's own SKILL.md (the self-install bootstrap served to
4
+ a fresh session that just needs to `pip install` and run plain CLI commands).
5
+ This installs the much larger orchestrator skill — vision workshop, parallel
6
+ build via the Workflow tool, escalation handling, multi-project status — as
7
+ a real Claude Code skill at ~/.claude/skills/pcp/SKILL.md, so `/pcp` becomes
8
+ available as a slash command.
9
+
10
+ Bundled as package data (src/pcp/skill_data/pcp/SKILL.md) rather than
11
+ downloaded, so it installs offline together with the wheel.
12
+ """
13
+
14
+ import shutil
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ import click
19
+ from rich.console import Console
20
+
21
+ console = Console()
22
+
23
+ BUNDLED_SKILL_PATH = Path(__file__).parent.parent / "skill_data" / "pcp" / "SKILL.md"
24
+ DEFAULT_INSTALL_PATH = Path.home() / ".claude" / "skills" / "pcp" / "SKILL.md"
25
+
26
+
27
+ @click.command(name="install-skill")
28
+ @click.option("--force", is_flag=True, help="Overwrite an existing installed skill.")
29
+ @click.option("--path", "install_path", type=click.Path(), default=None,
30
+ help="Override the install destination (default: ~/.claude/skills/pcp/SKILL.md).")
31
+ def install_skill(force: bool, install_path: str | None):
32
+ """Install the /pcp orchestrator skill to ~/.claude/skills/pcp/SKILL.md."""
33
+ if not BUNDLED_SKILL_PATH.exists():
34
+ console.print(f"[red]Error:[/red] bundled skill not found at {BUNDLED_SKILL_PATH} "
35
+ "— this install may be missing package data.")
36
+ sys.exit(2)
37
+
38
+ dest = Path(install_path) if install_path else DEFAULT_INSTALL_PATH
39
+
40
+ if dest.exists() and not force:
41
+ console.print(f"[yellow]Already installed:[/yellow] {dest}")
42
+ console.print("Use --force to overwrite with the bundled version.")
43
+ sys.exit(1)
44
+
45
+ dest.parent.mkdir(parents=True, exist_ok=True)
46
+ shutil.copy(BUNDLED_SKILL_PATH, dest)
47
+ console.print(f"[green]installed[/green] {dest}")
48
+ console.print("[dim]/pcp is now available as a Claude Code skill.[/dim]")