@optitransfer/proofline 0.0.0-stage → 0.2.0-next.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/AGENTS.md +32 -0
- package/CHANGELOG.md +36 -0
- package/CONTRIBUTING.md +29 -0
- package/LICENSE +154 -0
- package/NOTICE +10 -0
- package/PATENTS-NONASSERT.md +1 -0
- package/README.md +192 -2
- package/bin/proofline.mjs +97 -0
- package/docs/00-index.md +43 -0
- package/docs/01-scope.md +70 -0
- package/docs/02-requirements.md +64 -0
- package/docs/03-domain-model.md +43 -0
- package/docs/04-architecture.md +79 -0
- package/docs/05-lifecycle.md +47 -0
- package/docs/06-verification.md +57 -0
- package/docs/07-evidence.md +69 -0
- package/docs/08-adapters.md +61 -0
- package/docs/09-security.md +64 -0
- package/docs/10-conformance.md +59 -0
- package/docs/11-development-pipeline.md +50 -0
- package/docs/12-roadmap.md +104 -0
- package/docs/13-interdependencies.md +53 -0
- package/docs/14-metrics.md +40 -0
- package/docs/adr/0001-product-boundary.md +28 -0
- package/docs/adr/0002-evidence-and-gates.md +22 -0
- package/docs/adr/0003-standalone-first.md +18 -0
- package/package.json +57 -4
- package/src/cache.mjs +81 -0
- package/src/checks.mjs +271 -0
- package/src/claims.mjs +60 -0
- package/src/contract.mjs +163 -0
- package/src/engine.mjs +329 -0
- package/src/evidence.mjs +45 -0
- package/src/forbidden.mjs +57 -0
- package/src/index.mjs +203 -0
- package/src/mcp.mjs +55 -0
- package/src/util.mjs +92 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# AGENTS.md: the loop a coding agent follows in a repository that uses proofline (copy this file into that repository)
|
|
2
|
+
|
|
3
|
+
Proofline decides whether your work is done. Your report is a claim; Proofline re-runs every check itself.
|
|
4
|
+
|
|
5
|
+
## Loop (every task, no exceptions)
|
|
6
|
+
|
|
7
|
+
1. `proofline next` (MCP: `task_next`). Work only on the task it returns. Read the whole brief. If the contract has owners, add `--owner <your name>` (MCP: `owner`) so you only get your tasks and unowned ones.
|
|
8
|
+
2. Write the failing test(s) first. Run them and keep the failing output.
|
|
9
|
+
3. Implement the root-cause fix **on the default path** (wired in, default config). Code that is built but unreachable does not count.
|
|
10
|
+
4. `proofline verify <TASK>` (MCP: `verify`). Read the corrective instruction. Fix and repeat until `DECISION: PASS`.
|
|
11
|
+
Max 3 corrective cycles; then report `blocked` with root cause (file:line), what you tried, what you need.
|
|
12
|
+
5. Commit (one task per commit) and push to the contract's remote and branch (`defaults.remote`, default `origin`; `defaults.branch`, default `main`) with hooks on. The pre-push hook re-gates the task. You may end the commit message with the trailer `Proofline-Base: <sha>` (the base the brief names), so a fresh clone gates against the same base.
|
|
13
|
+
6. `proofline gate <TASK> --claims "<report line>"` (MCP: `gate` with `claims`). Paste the report. In a clone where `next` did not run, add `--base <sha>` (MCP: `base`); without a base the gate is BLOCKED, never PASS.
|
|
14
|
+
7. Back to step 1. The closing text of each gate says which step is next: push (PASS, not pushed yet), report with claims (PASS on the pushed HEAD), or `proofline next` (claims accepted).
|
|
15
|
+
|
|
16
|
+
## Report line (exact)
|
|
17
|
+
|
|
18
|
+
`TASK-ID | done/partial/not done/blocked | <commit> | <test names> | <metric=value vs target> | <what is missing>`
|
|
19
|
+
|
|
20
|
+
Proofline rejects: "done" without a commit, test names or measured numbers; numbers that differ from its own measurement (>15%); commits not on the contract's push branch; tests that don't exist; hedged or deferred wording ("should work", "likely", "follow-up", "later", "out of scope").
|
|
21
|
+
|
|
22
|
+
## Never
|
|
23
|
+
|
|
24
|
+
- Skip/only/todo tests, raise timeouts, delete tests or assertions, edit fixtures to pass (unless the fixture is the proven root cause, with proof).
|
|
25
|
+
- Lower a target, cap or test size, or touch thresholds/commands/criteria in `proofline.json` (detected; BLOCKED until the owner approves).
|
|
26
|
+
- `--no-verify`, bypass env vars, skip flags. There is no bypass. If CI runs `proofline gate --ci`, it re-runs the gate.
|
|
27
|
+
- Say "done" for anything that is not PASS on the pushed HEAD.
|
|
28
|
+
- Defer. Only the owner approves a deferral.
|
|
29
|
+
|
|
30
|
+
## Stop rules
|
|
31
|
+
|
|
32
|
+
If a metric misses its threshold: STOP. Report the measured number, the per-stage trace and file:line. Do not start the next task.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable project changes are recorded here. Dates use ISO 8601 (`YYYY-MM-DD`).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- `contract create` keeps one level of `scope.allow` when the task file writes the scope as the contract does, `{ "allow": [...] }`, and `contract validate` refuses a scope that is not a list of patterns, so `next` cannot crash on it (#19).
|
|
9
|
+
- A failed `command` criterion's evidence names the failed tests. `out_tail` keeps the last 1,200 characters, where node's summary of repeated failures with their stacks held the last two or three names. The new `failing` field lists the failed test names found anywhere in the output (TAP `not ok` lines, the spec reporter's `✖ <name> (<ms>ms)` lines and the `failing tests:` section), without todos, skips or repeats, in order of first appearance and at most 100, with `failing_total` when there were more. The FAIL detail names the first 10, a flaky result keeps both runs' lists, and the gate report lists the names under Failing tests (#13).
|
|
10
|
+
- The forbidden scan no longer reads `proofline.json` or `.proofline/`: the commit that adds or amends the contract failed on its own deny-list. Contract changes stay covered by the weakening check, and the gate report says whether the contract was added, changed or unchanged vs the base (#4).
|
|
11
|
+
- `init` no longer replaces an existing `pre-commit` or `pre-push` hook: it keeps it as `<hook>.proofline-prev`, and the installed hook runs it first (same arguments and stdin) and stops on its failure. A second `init` changes nothing (#1).
|
|
12
|
+
- The corrective text for an unpushed task and the brief name the push target from the criterion's `remote`/`branch`, else `defaults.remote`/`defaults.branch`, instead of always `origin` (#8).
|
|
13
|
+
- A PASS no longer always says "Commit, push to main ... Then call task_next". On a HEAD already on the push target it says the task is verified on the pushed HEAD and gives the `gate` command with claims (and `--base` when one was passed); after accepted claims it points to `proofline next`; only an unpushed PASS says to push, to the configured remote and branch (#9).
|
|
14
|
+
- A verify or gate with no pinned base, where the default base is HEAD (a fresh clone after the push), is BLOCKED instead of passing every diff check on an empty diff (#5).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `@optitransfer/proofline@0.2.0-next.0` is the public package on the `next` dist-tag. `private` is false, the test script runs `node --test test/*.test.mjs`, and `files` lists what this revision ships. The sixty-second start installs that package and calls `npx proofline`. The package description, keywords, homepage, repository, bugs and author match the GitHub repository page.
|
|
18
|
+
- The pre-commit hook accepts a contract change only when an approval marker is present. The marker is a file named with the sha256 of the staged `proofline.json` bytes, in the directory set by `init --approvals` and stored in `.proofline/config.json` (never committed). The refusal prints the weakenings, the sha256 and the marker path. A marker for other bytes does not count, what else is staged does not matter, and with no directory configured every contract change is refused. `verify` and `gate` use the same marker for the current `proofline.json` bytes: a regular file approves the weakenings, the report names the marker path, and the decision is not blocked.
|
|
19
|
+
- Opt-in result reuse: with `defaults.reuse_results` (validated, default `false`), a `command` or `metric` PASS on a clean tree is recorded in the evidence log and reused by a later `verify` or `gate` on the same HEAD, so `verify`, `gate` and the pre-push hook run the full suite once per commit. The key is the verifier, HEAD, `process.version` and the env vars the command names; only a PASS is stored; `defaults.reuse_max_age_h` (default 24) bounds its age; a metric is reused only within `max_load_per_cpu`. A result is reused only from an evidence entry still in an intact chain with the hash the cache file stored. Reused criteria say `reused from <time> (evidence entry <n>)` and are listed apart in the gate report and its evidence entry. `--fresh` on `verify` and `gate` (MCP: `fresh`) runs every check. Turning reuse on or off is not a weakening (#12).
|
|
20
|
+
- Metric evidence and report lines record the load before the run, the CPU count and the `node --version` the bench command resolves. Optional `defaults.max_load_per_cpu`: a metric run on a busier machine is BLOCKED (machine busy, with the numbers) and the bench is not run; raising or removing the limit is a weakening (#7).
|
|
21
|
+
- `defaults.remote` in the contract (validated as a string), used by `pushed`, by claim reconciliation and by the default base, which is now `defaults.base`, else `<remote>/<branch>`. A repository whose remote is not `origin` no longer gets a BLOCKED gate on an unresolved base, or a claim check that silently checks nothing (#6).
|
|
22
|
+
- Optional `owner` on a task and `defaults.owners` in the contract (validated); `--owner <name>` on `next`, `status` and `brief` (MCP: `task_next` and `status`). `next --owner` hands out that owner's tasks and unowned ones, never another owner's (#2).
|
|
23
|
+
- `--base <rev>` on `verify` and `gate` (MCP: `base`), an optional `Proofline-Base:` commit trailer, and the base source (`--base`, contract, pinned at start, commit trailer, default) in every report. The `next` brief prints the pinned base (#5).
|
|
24
|
+
- `brief <TASK> --base <rev>`: the brief ends with the base `verify` and `gate` will use, its source, and the reason it would be BLOCKED, if any (#5).
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - 2026-10-14
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- Executable MVP: CLI (`init`, `contract`, `next`, `brief`, `verify`, `gate`, `report`, `status`, `evidence`, `hook`, `mcp`).
|
|
30
|
+
- Contract schema `proofline/1` with ten deterministic verifier types, phase + dependency task graph, contract-weakening detection.
|
|
31
|
+
- Hash-chained append-only evidence log; red-green replay in a git worktree; flaky re-run policy; baseline regression check.
|
|
32
|
+
- Claim reconciliation of `TASK | status | commit | tests | numbers | missing` lines; corrective prompts per failed criterion.
|
|
33
|
+
- MCP stdio server, git pre-commit/pre-push hooks with no bypass variable, GitHub Actions self-gate.
|
|
34
|
+
- AGENTS.md agent loop, FAIL to PASS demo.
|
|
35
|
+
- Initial product scope, requirements, architecture, lifecycle, evidence model, security posture, verification strategy, delivery pipeline, roadmap, and dependency map.
|
|
36
|
+
- Initial Mermaid diagrams, ADRs, task-contract example, and project templates.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Current phase
|
|
4
|
+
|
|
5
|
+
The repository holds the CLI, the MCP server, the engine and their tests. Changes to scope, requirements, architecture, security, or acceptance criteria should be explicit and traceable.
|
|
6
|
+
|
|
7
|
+
## Change process
|
|
8
|
+
|
|
9
|
+
1. State the problem and the requirement(s) affected.
|
|
10
|
+
2. Record assumptions and exclusions.
|
|
11
|
+
3. Update the relevant specification and, if needed, an ADR.
|
|
12
|
+
4. Add or update conformance cases for behaviour changes.
|
|
13
|
+
5. Update the roadmap and changelog when status or commitments change.
|
|
14
|
+
6. Do not label a capability implemented until code and repeatable evidence exist.
|
|
15
|
+
|
|
16
|
+
## Design review checklist
|
|
17
|
+
|
|
18
|
+
- Does the change preserve the separation between agent claims and independent evidence?
|
|
19
|
+
- Can it be used without any optional integration (context store, agent SDK, network)?
|
|
20
|
+
- Does it distinguish pass, fail, skipped, not run, and inconclusive?
|
|
21
|
+
- Does it introduce unbounded retries, hidden side effects, or excessive interruption?
|
|
22
|
+
- Are permission boundaries and secret handling explicit?
|
|
23
|
+
- Can the outcome be reproduced from the report and captured revision?
|
|
24
|
+
|
|
25
|
+
## Sign-off
|
|
26
|
+
|
|
27
|
+
Each commit needs a Developer Certificate of Origin sign-off (https://developercertificate.org/). Put this line in the commit message, with the name and email of the commit:
|
|
28
|
+
|
|
29
|
+
Signed-off-by: Name <email>
|
package/LICENSE
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all other
|
|
16
|
+
entities that control, are controlled by, or are under common control
|
|
17
|
+
with that entity. For the purposes of this definition, "control" means
|
|
18
|
+
(i) the power, direct or indirect, to cause the direction or management
|
|
19
|
+
of such entity, whether by contract or otherwise, or (ii) ownership of
|
|
20
|
+
fifty percent (50%) or more of the outstanding shares, or (iii)
|
|
21
|
+
beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
24
|
+
permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation source,
|
|
28
|
+
and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but not
|
|
32
|
+
limited to compiled object code, generated documentation, and
|
|
33
|
+
conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or Object
|
|
36
|
+
form, made available under the License, as indicated by a copyright
|
|
37
|
+
notice that is included in or attached to the work (an example is
|
|
38
|
+
provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including the
|
|
49
|
+
original version of the Work and any modifications or additions to that
|
|
50
|
+
Work or Derivative Works thereof, that is intentionally submitted to
|
|
51
|
+
Licensor for inclusion in the Work by the copyright owner or by an
|
|
52
|
+
individual or Legal Entity authorized to submit on behalf of the
|
|
53
|
+
copyright owner. For the purposes of this definition, "submitted" means
|
|
54
|
+
any form of electronic, verbal, or written communication sent to the
|
|
55
|
+
Licensor or its representatives, including communication on electronic
|
|
56
|
+
mailing lists, source code control systems, and issue tracking systems
|
|
57
|
+
that are managed by, or on behalf of, the Licensor for the purpose of
|
|
58
|
+
discussing and improving the Work, but excluding communication that is
|
|
59
|
+
conspicuously marked or otherwise designated in writing by the copyright
|
|
60
|
+
owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of this
|
|
67
|
+
License, each Contributor hereby grants to You a perpetual, worldwide,
|
|
68
|
+
non-exclusive, no-charge, royalty-free, irrevocable copyright license
|
|
69
|
+
to reproduce, prepare Derivative Works of, publicly display, publicly
|
|
70
|
+
perform, sublicense, and distribute the Work and such Derivative Works
|
|
71
|
+
in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of this
|
|
74
|
+
License, each Contributor hereby grants to You a perpetual, worldwide,
|
|
75
|
+
non-exclusive, no-charge, royalty-free, irrevocable (except as stated in
|
|
76
|
+
this section) patent license to make, have made, use, offer to sell,
|
|
77
|
+
sell, import, and otherwise transfer the Work, where such license applies
|
|
78
|
+
only to those patent claims licensable by such Contributor that are
|
|
79
|
+
necessarily infringed by their Contribution(s) alone or by combination
|
|
80
|
+
of their Contribution(s) with the Work to which such Contribution(s) was
|
|
81
|
+
submitted. If You institute patent litigation against any entity
|
|
82
|
+
(including a cross-claim or counterclaim in a lawsuit) alleging that the
|
|
83
|
+
Work or a Contribution incorporated within the Work constitutes direct
|
|
84
|
+
or contributory patent infringement, then any patent licenses granted
|
|
85
|
+
to You under this License for that Work shall terminate as of the date
|
|
86
|
+
such litigation is filed.
|
|
87
|
+
|
|
88
|
+
4. Redistribution. You may reproduce and distribute copies of the Work or
|
|
89
|
+
Derivative Works thereof in any medium, with or without modifications,
|
|
90
|
+
and in Source or Object form, provided that You meet the following
|
|
91
|
+
conditions:
|
|
92
|
+
(a) You must give any other recipients of the Work or Derivative Works
|
|
93
|
+
a copy of this License; and
|
|
94
|
+
(b) You must cause any modified files to carry prominent notices stating
|
|
95
|
+
that You changed the files; and
|
|
96
|
+
(c) You must retain, in the Source form of any Derivative Works that
|
|
97
|
+
You distribute, all copyright, patent, trademark, and attribution
|
|
98
|
+
notices from the Source form of the Work, excluding notices that do
|
|
99
|
+
not pertain to any part of the Derivative Works; and
|
|
100
|
+
(d) If the Work includes a "NOTICE" text file as part of its distribution,
|
|
101
|
+
then any Derivative Works that You distribute must include a readable
|
|
102
|
+
copy of the attribution notices contained within such NOTICE file,
|
|
103
|
+
excluding those notices that do not pertain to any part of the
|
|
104
|
+
Derivative Works.
|
|
105
|
+
You may add Your own copyright statement to Your modifications and may
|
|
106
|
+
provide additional or different license terms and conditions for use,
|
|
107
|
+
reproduction, or distribution of Your modifications, or for any such
|
|
108
|
+
Derivative Works as a whole, provided Your use, reproduction, and
|
|
109
|
+
distribution of the Work otherwise complies with the conditions stated
|
|
110
|
+
in this License.
|
|
111
|
+
|
|
112
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
113
|
+
any Contribution intentionally submitted for inclusion in the Work by You
|
|
114
|
+
to the Licensor shall be under the terms and conditions of this License,
|
|
115
|
+
without any additional terms or conditions. Notwithstanding the above,
|
|
116
|
+
nothing herein shall supersede or modify the terms of any separate
|
|
117
|
+
license agreement you may have executed with Licensor regarding such
|
|
118
|
+
Contributions.
|
|
119
|
+
|
|
120
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
121
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
122
|
+
except as required for reasonable and customary use in describing the
|
|
123
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
124
|
+
|
|
125
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to
|
|
126
|
+
in writing, Licensor provides the Work (and each Contributor provides
|
|
127
|
+
its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
|
128
|
+
OF ANY KIND, either express or implied, including, without limitation,
|
|
129
|
+
any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY,
|
|
130
|
+
or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for
|
|
131
|
+
determining the appropriateness of using or redistributing the Work and
|
|
132
|
+
assume any risks associated with Your exercise of permissions under this License.
|
|
133
|
+
|
|
134
|
+
8. Limitation of Liability. In no event and under no legal theory, whether
|
|
135
|
+
in tort (including negligence), contract, or otherwise, unless required
|
|
136
|
+
by applicable law or agreed to in writing, shall any Contributor be liable
|
|
137
|
+
to You for damages, including any direct, indirect, special, incidental,
|
|
138
|
+
or consequential damages of any character arising as a result of this
|
|
139
|
+
License or out of the use or inability to use the Work (including but not
|
|
140
|
+
limited to damages for loss of goodwill, work stoppage, computer failure
|
|
141
|
+
or malfunction, or any and all other commercial damages or losses), even
|
|
142
|
+
if such Contributor has been advised of the possibility of such damages.
|
|
143
|
+
|
|
144
|
+
9. Accepting Warranty or Additional Liability. While redistributing the Work
|
|
145
|
+
or Derivative Works thereof, You may choose to offer, and charge a fee for,
|
|
146
|
+
acceptance of support, warranty, indemnity, or other liability obligations
|
|
147
|
+
and/or rights consistent with this License. However, in accepting such
|
|
148
|
+
obligations, You may act only on Your own behalf and on Your sole
|
|
149
|
+
responsibility, not on behalf of any other Contributor, and only if You
|
|
150
|
+
agree to indemnify, defend, and hold each Contributor harmless for any
|
|
151
|
+
liability incurred by, or claims asserted against, such Contributor by
|
|
152
|
+
reason of your accepting any such warranty or additional liability.
|
|
153
|
+
|
|
154
|
+
END OF TERMS AND CONDITIONS
|
package/NOTICE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
proofline
|
|
2
|
+
Copyright 2026 Ryan Gillespie / Optitransfer
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0. See LICENSE.
|
|
5
|
+
|
|
6
|
+
"proofline" is a trademark of Ryan Gillespie / Optitransfer. The Apache License 2.0 (Section 6)
|
|
7
|
+
grants no trademark rights: any fork or a derived product uses its own name, and may say that it
|
|
8
|
+
is derived from proofline.
|
|
9
|
+
Contributions are accepted under the Apache License 2.0 with a Developer Certificate of Origin
|
|
10
|
+
sign-off on each commit (CONTRIBUTING.md); there is no contributor licence agreement.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Apache-2.0 section 3 grants the patent licence for contributions to this project. A fuller non-assert follows counsel review, and the entity signs that covenant.
|
package/README.md
CHANGED
|
@@ -1,3 +1,193 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Proofline
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**The agent says done. Proofline verifies and validates the work against a V&V contract, to assure agent performance.**
|
|
4
|
+
|
|
5
|
+
A coding agent reports that a task is finished. Proofline does not take the report: it holds the definition of done as a contract a machine can check, hands the agent one task at a time, re-runs every check itself when the agent reports, and lets a commit through only when the evidence says PASS. The agent gets a corrective prompt that names the file and the line to fix next, never a verdict it can argue with. Zero dependencies, Node 22+, Apache-2.0.
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart LR
|
|
9
|
+
C["proofline.json<br/>tasks, criteria, scope,<br/>forbidden actions"] --> N["proofline next<br/>one task, its brief,<br/>the base revision pinned"]
|
|
10
|
+
N --> A["the agent<br/>failing test first,<br/>then the fix"]
|
|
11
|
+
A --> H1{"pre-commit hook<br/>forbidden actions,<br/>contract change without a marker"}
|
|
12
|
+
H1 -- "refused" --> A
|
|
13
|
+
H1 -- "clean" --> V["proofline verify<br/>every criterion re-run<br/>from the base revision"]
|
|
14
|
+
V -- "FAIL or BLOCKED:<br/>corrective prompt,<br/>file and line" --> A
|
|
15
|
+
V -- "PASS" --> G["proofline gate --claims<br/>the agent's report line<br/>against the evidence"]
|
|
16
|
+
G -- "a claim the evidence<br/>does not support" --> A
|
|
17
|
+
G -- "PASS, report written" --> H2{"pre-push hook<br/>does this HEAD gate PASS?"}
|
|
18
|
+
H2 -- "no" --> A
|
|
19
|
+
H2 -- "yes" --> M[("main")]
|
|
20
|
+
M --> N
|
|
21
|
+
O["owner marker<br/>sha256 of the approved<br/>proofline.json"] -. "approves a<br/>contract change" .-> H1
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Sixty seconds
|
|
25
|
+
|
|
26
|
+
In the repository the agent works on:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install --save-dev @optitransfer/proofline@next && npx proofline init --approvals ~/.proofline/approved && cp node_modules/@optitransfer/proofline/AGENTS.md .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
initialised /path/to/your-repo
|
|
34
|
+
proofline.json
|
|
35
|
+
.proofline/config.json
|
|
36
|
+
.gitignore
|
|
37
|
+
hooks/pre-commit
|
|
38
|
+
hooks/pre-push
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That writes the contract skeleton, the local evidence log, the two git hooks, the directory where you (the owner) approve contract changes, and the loop the agent follows in `AGENTS.md`. Add one task and tell the agent to start:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cat > task.json <<'JSON'
|
|
45
|
+
{ "id": "TASK-1", "title": "Reject an empty body", "phase": "implement",
|
|
46
|
+
"scope": { "allow": ["src/**", "test/**", "CHANGELOG.md"] },
|
|
47
|
+
"criteria": [
|
|
48
|
+
{ "id": "TASK-1-AC01", "mandatory": true, "statement": "an empty body is refused",
|
|
49
|
+
"verifier": { "type": "test_red_green", "test_file": "test/empty_body.test.mjs", "cmd": "node --test {test}" } },
|
|
50
|
+
{ "id": "TASK-1-AC02", "mandatory": true, "statement": "suite green",
|
|
51
|
+
"verifier": { "type": "command", "cmd": "npm test" } },
|
|
52
|
+
{ "id": "TASK-1-AC03", "mandatory": true, "statement": "pushed", "verifier": { "type": "pushed" } } ] }
|
|
53
|
+
JSON
|
|
54
|
+
npx proofline contract create --from task.json # TASK-1: valid
|
|
55
|
+
npx proofline next # the agent's first command: the brief, with the base pinned
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
When the agent reports, Proofline checks. Before any work this is what it says, and the agent is told what to do next:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
$ npx proofline verify TASK-1
|
|
62
|
+
== TASK-1: FAIL
|
|
63
|
+
base: 0a88576db5567f08c9f1cd9481c633001036d8e1 = 0a88576d (pinned at start)
|
|
64
|
+
FAIL TASK-1-AC01 [test_red_green] test file test/empty_body.test.mjs not found
|
|
65
|
+
PASS TASK-1-AC02 [command] exit 0 in 393 ms
|
|
66
|
+
FAIL TASK-1-AC03 [pushed] working tree not clean (3 paths); work held locally
|
|
67
|
+
report: .proofline/reports/TASK-1-latest.md
|
|
68
|
+
|
|
69
|
+
PROOFLINE CORRECTIVE INSTRUCTION for TASK-1 (Reject an empty body): gate FAIL
|
|
70
|
+
1. TASK-1-AC01 [test_red_green] FAIL: an empty body is refused
|
|
71
|
+
Next step: The test test/empty_body.test.mjs must FAIL on the base and PASS now.
|
|
72
|
+
2. TASK-1-AC03 [pushed] FAIL: pushed
|
|
73
|
+
Next step: Commit everything and `git push origin main` (hooks on; never --no-verify).
|
|
74
|
+
DECISION: FAIL
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Exit codes: `0` PASS, `1` FAIL, `2` BLOCKED, `3` usage. Every command takes `--json`. The published package is `@optitransfer/proofline` at `0.2.0-next.0` on the `next` dist-tag: `npx @optitransfer/proofline@next`.
|
|
78
|
+
|
|
79
|
+
## Why
|
|
80
|
+
|
|
81
|
+
An agent's report is a claim. Left to itself it says "done" for a test it skipped, a timeout it raised, a target it lowered, a commit it never pushed, or a number it did not measure. Reviewing every claim by hand does not scale past one agent, and asking a second model whether the first one is right only adds a second opinion.
|
|
82
|
+
|
|
83
|
+
Proofline replaces the opinion with a contract and a re-run. The contract is yours: it names each task, the files it may touch, and the checks that prove it, and it cannot be weakened by the agent, because a changed criterion, threshold or forbidden rule is refused at commit time unless a marker with the hash of the new contract sits in the directory only you write to. The checks are the kind a machine can settle: a test that fails on the base revision and passes now, a command and its exit code, a metric against its target, changed files inside the allowed scope, a HEAD that is on the contract's branch. Every result is appended to a hash-chained evidence log, so a report can be checked later against what actually ran.
|
|
84
|
+
|
|
85
|
+
## What it checks
|
|
86
|
+
|
|
87
|
+
Every criterion names a verifier. Each one is deterministic: no model reads the diff, no opinion decides.
|
|
88
|
+
|
|
89
|
+
| Type | Passes only when |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `command` | exit code matches; no hung/cancelled tests; optional `max_skipped`, `min_tests` (catches deleted tests). A fail-then-pass is **flaky**, not a pass. Test counts and failing-test names are parsed from the Node test runner's output (TAP or spec reporter); for other runners use `command` criteria without `min_tests` |
|
|
92
|
+
| `test_red_green` | the test exists, **fails** on the base revision (replayed in a git worktree) and **passes** now; named test actually executed |
|
|
93
|
+
| `test_exists` | test file (and name) present |
|
|
94
|
+
| `metric` | bench command's JSON (`key` dotted path) meets `op value`; also fails on >20% regression vs `baseline.metrics`. One bench run feeds all its metrics. Missing rig → BLOCKED. The evidence and report line record the 1-minute load, the CPU count and the `node --version` the command resolves; with `defaults.max_load_per_cpu` set, a load per CPU above it before the run is BLOCKED (machine busy), never PASS or FAIL, and raising or removing that limit is a contract weakening |
|
|
95
|
+
| `grep` | pattern present/absent (e.g. the new code path is the default config) |
|
|
96
|
+
| `wiring` | the symbol is **called** in a file reachable by static imports from the default entry (catches "built but not wired in") |
|
|
97
|
+
| `scope` | every changed file matches the allowlist |
|
|
98
|
+
| `docs` | generator leaves no diff in the given paths |
|
|
99
|
+
| `pushed` | tree clean and HEAD == `<remote>/<branch>` (the criterion's `remote`/`branch`, else `defaults.remote`/`defaults.branch`, else `origin`/`main`) |
|
|
100
|
+
| `file_exists` | path exists (e.g. the bench rig is committed) |
|
|
101
|
+
|
|
102
|
+
Always on: forbidden-action scan of the diff (skip/only/todo, raised timeouts, net assertion loss, deleted tests, `--no-verify`, bypass env vars, lowered protected caps; `proofline.json` and `.proofline/` are left out, because the contract declares the deny-list and would match it), contract-weakening check vs the base revision (loosened thresholds, removed criteria, changed commands → BLOCKED), hash-chained evidence log integrity, claim reconciliation.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## How it decides
|
|
106
|
+
|
|
107
|
+
### Owners
|
|
108
|
+
|
|
109
|
+
Two or more builders can share one contract. A task may carry an `owner`, and `defaults.owners` may list the names (`contract validate` then refuses an owner outside the list). `next --owner <name>` (MCP: `task_next` with `owner`) hands out only that owner's tasks and unowned ones, never another owner's; the phase gate applies to that lane, and `depends_on` still waits on any owner's task. `status --owner <name>` and `brief <TASK> --owner <name>` take the same filter, and `brief` refuses another owner's task. Without `--owner`, nothing changes.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Git hooks
|
|
113
|
+
|
|
114
|
+
`init` installs `pre-commit` (forbidden actions and contract weakening vs HEAD) and `pre-push` (the active task must gate PASS). If the repository already has one of these hooks and it is not proofline's, `init` keeps it as `<hook>.proofline-prev` and says so. The installed hook runs it first, with the same arguments and stdin; if it fails, its exit code stands and the gate does not run. Running `init` again changes nothing. If both `<hook>` and `<hook>.proofline-prev` exist and neither is proofline's, `init` stops rather than lose one.
|
|
115
|
+
|
|
116
|
+
A contract change is refused unless an approval marker is present. `proofline init --approvals <dir>` (re-runnable) stores that directory in `.proofline/config.json`, which is never committed. The marker is a file in that directory named with the sha256 of the staged `proofline.json` bytes. The refusal prints the weakenings, the sha256 and the marker path. A marker named for any other bytes does not count, and what else the commit stages makes no difference. With no directory configured, every contract change is refused. There is no bypass.
|
|
117
|
+
|
|
118
|
+
`verify` and `gate` read the same marker for the current `proofline.json` bytes. When that marker is a regular file, the weakenings are an approved contract change: the report names the marker path, and the decision is not blocked. A missing marker, a marker for other bytes, or a symlink still blocks.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
### Base revision
|
|
122
|
+
|
|
123
|
+
Every diff check (forbidden scan, `scope`, `test_red_green`, contract weakening) compares the base revision with the working tree. The base is taken from, first match wins:
|
|
124
|
+
|
|
125
|
+
1. `--base <rev>` on `verify` or `gate` (MCP: `base`);
|
|
126
|
+
2. the task's own `base` in the contract;
|
|
127
|
+
3. the base pinned when the task started (`proofline next` records it in the local `.proofline/` log, and the brief prints it);
|
|
128
|
+
4. a `Proofline-Base: <sha>` trailer on the HEAD commit (optional; it must name an ancestor of HEAD, or the gate is BLOCKED);
|
|
129
|
+
5. the default: `defaults.base`, else `<defaults.remote>/<defaults.branch>` (`origin/main` when neither is set).
|
|
130
|
+
|
|
131
|
+
A repository whose remote is not called `origin` sets `defaults.remote` (for example `"github"`) and leaves `defaults.base` out; `pushed`, claim reconciliation and the default base then use that remote.
|
|
132
|
+
|
|
133
|
+
Every report says which one it used, and `brief <TASK> [--base <rev>]` says which base `verify` and `gate` will use and whether it would block. A fresh clone has no log, and after the push the default base is HEAD, so the diff is empty and every diff check would pass on nothing. In that case the gate is BLOCKED, never PASS, and the corrective line asks for `--base <rev>` (for one commit, its parent) or `proofline next`. The CI workflow passes the previous tip of the pushed branch as `--base`.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
### Reusing results (verify, then gate)
|
|
137
|
+
|
|
138
|
+
`verify` and `gate` run every check each time. When a task's slowest check is the full suite, `verify`, `gate` and the pre-push hook on the same commit run it two or three times with nothing changed. A contract can opt in to reuse:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
"defaults": { "reuse_results": true, "reuse_max_age_h": 24 }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- `reuse_results` (`true`/`false`, default `false`): with it off, or left out, every check runs every time, as before. Turning it on or off is not a contract weakening.
|
|
145
|
+
- Only `command` and `metric` results are reused. `test_red_green`, `scope`, `wiring`, `grep`, `docs`, `pushed`, the forbidden scan and the weakening check always run: they are cheap, or they depend on the base.
|
|
146
|
+
- Only a PASS is stored. A FAIL, a flaky result, a busy-machine block or an error runs again every time.
|
|
147
|
+
- A PASS is reused only on the same HEAD with a clean tree: `git status` shows nothing outside `.proofline/`, untracked files included. An uncommitted edit never reuses a result, and a check run on a dirty tree is not stored.
|
|
148
|
+
- The key is the criterion's verifier object (canonical JSON), the HEAD sha, `process.version` and the values of the env vars the command names (`$VAR`, `${VAR}`; `{VAR}` in a metric's `output_file`); for a metric also its baseline and the regression tolerance. Change any of them and the check runs again. Gitignored files, env vars the command does not name and tools outside the repo are not in the key: a check that depends on them needs `--fresh`.
|
|
149
|
+
- `reuse_max_age_h` (a positive number, default 24): an older PASS runs again.
|
|
150
|
+
- A metric PASS is reused only if the load per CPU it recorded is within `defaults.max_load_per_cpu`, when that is set. Its measured value is reused with it, so claims are checked against it.
|
|
151
|
+
- A fresh PASS is recorded in the evidence log (a `result` entry) and `.proofline/cache/<sha256 of key>.json` points at that entry with its hash, time, status, detail and evidence (exit code, test counts, duration). A result is reused only from that entry, and only while it is in an intact chain with the same hash; an edited or truncated log, or a cache file that names another hash, means the check runs again.
|
|
152
|
+
- Every reused criterion says `reused from <time> (evidence entry <n>)` in its report line, the gate report lists reused criteria under **Reused results** (not run again in that gate), and the gate's evidence entry carries the same lines.
|
|
153
|
+
- `--fresh` on `verify` and `gate` (MCP: `fresh: true`) ignores stored results and runs every check; a PASS it gets replaces the stored one.
|
|
154
|
+
|
|
155
|
+
Demo (claimed done with no evidence, FAIL, then real work, PASS): `npm run demo`.
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
## For agents
|
|
159
|
+
|
|
160
|
+
Read [AGENTS.md](AGENTS.md): the loop, the exact report line, what is never allowed and the stop rules. MCP config: [examples/mcp-config.json](examples/mcp-config.json). Tools: `contract_create`, `task_next`, `evidence_record`, `verify`, `gate`, `report`, `status`.
|
|
161
|
+
|
|
162
|
+
## Repository layout
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
proofline/
|
|
166
|
+
├── bin/proofline.mjs the command: init, contract, next, brief, verify, gate, report, status, evidence, hook, mcp
|
|
167
|
+
├── src/contract.mjs contract schema, validation, task graph, weakening detection
|
|
168
|
+
├── src/checks.mjs the verifiers
|
|
169
|
+
├── src/forbidden.mjs the forbidden-action scan
|
|
170
|
+
├── src/claims.mjs report-line parser and claim reconciliation
|
|
171
|
+
├── src/evidence.mjs the append-only, hash-chained evidence log (.proofline/evidence.jsonl)
|
|
172
|
+
├── src/engine.mjs verify, the corrective prompt, gate and its report, next and brief, phases
|
|
173
|
+
├── src/mcp.mjs the MCP server on stdio
|
|
174
|
+
├── src/index.mjs the API, init, hooks
|
|
175
|
+
├── examples/demo/ the FAIL to PASS demo (npm run demo)
|
|
176
|
+
├── examples/demo-contract/ an example contract with one task; the self-gate validates it
|
|
177
|
+
├── docs/ the source design: scope, requirements, architecture, lifecycle, evidence, roadmap
|
|
178
|
+
├── plans/ definition of done, risk register, pilot plan
|
|
179
|
+
└── proofline.json Proofline's own contract: the repository gates itself
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Documentation
|
|
183
|
+
|
|
184
|
+
[docs/00-index.md](docs/00-index.md) lists the design documents. [CONTRIBUTING.md](CONTRIBUTING.md) has the sign-off rule; [SECURITY.md](SECURITY.md) says how to report a vulnerability.
|
|
185
|
+
|
|
186
|
+
## Known limits
|
|
187
|
+
|
|
188
|
+
See `KNOWN_LIMITS` in `src/engine.mjs`; they are printed in every report. In short: same-machine agents can edit local state (tampering is detected, and CI re-verifies, but protect `proofline.json` with branch protection/CODEOWNERS); `--no-verify` can skip local hooks (CI catches it); static wiring/forbidden checks are heuristics; contracts are JSON only; with `reuse_results` on, a reused PASS is keyed on HEAD, node and the env vars the command names, not on gitignored files or tools outside the repo.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
Apache-2.0 (see [LICENSE](LICENSE) and [NOTICE](NOTICE)). Contributions are accepted under the same licence with a Developer Certificate of Origin sign-off on each commit.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Proofline CLI. Exit codes: 0 PASS/ok, 1 FAIL, 2 BLOCKED, 3 usage error.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import * as P from '../src/index.mjs';
|
|
5
|
+
import { verifyChain, readLog } from '../src/evidence.mjs';
|
|
6
|
+
|
|
7
|
+
const argv = process.argv.slice(2);
|
|
8
|
+
const flags = {}; const pos = [];
|
|
9
|
+
const SWITCHES = ['fresh']; // never take a value: `verify --fresh T` keeps T as the task
|
|
10
|
+
for (let i = 0; i < argv.length; i++) {
|
|
11
|
+
const a = argv[i];
|
|
12
|
+
if (a.startsWith('--')) { const [k, v] = a.slice(2).split('='); if (v !== undefined) flags[k] = v; else if (!SWITCHES.includes(k) && argv[i + 1] && !argv[i + 1].startsWith('--')) flags[k] = argv[++i]; else flags[k] = true; }
|
|
13
|
+
else pos.push(a);
|
|
14
|
+
}
|
|
15
|
+
const root = flags.root ? flags.root : P.findRoot();
|
|
16
|
+
const json = !!flags.json;
|
|
17
|
+
const print = (obj, text) => { process.stdout.write((json || text === undefined ? JSON.stringify(obj, null, 2) : text) + '\n'); };
|
|
18
|
+
const code = (d) => (d === 'PASS' ? 0 : d === 'BLOCKED' ? 2 : 1);
|
|
19
|
+
const owner = () => { if (flags.owner === true) throw new Error('--owner needs a name'); return flags.owner; };
|
|
20
|
+
const base = () => { if (flags.base === true) throw new Error('--base needs a revision'); return flags.base; };
|
|
21
|
+
const claims = () => flags['claims-file'] ? fs.readFileSync(flags['claims-file'], 'utf8') : flags.claims === true ? fs.readFileSync(0, 'utf8') : flags.claims;
|
|
22
|
+
|
|
23
|
+
const HELP = `proofline — independent V&V and delivery controller
|
|
24
|
+
init [--no-hooks] [--approvals <dir>] create proofline.json, .proofline/, install git hooks;
|
|
25
|
+
--approvals sets the directory of owner markers (re-runnable)
|
|
26
|
+
contract create --from spec.json add a task contract (or --id --title [--phase] [--owner n] [--depends a,b] [--scope g1,g2])
|
|
27
|
+
contract validate | show
|
|
28
|
+
next [--owner <name>] the single next task with its full brief (marks it active);
|
|
29
|
+
--owner: only that owner's tasks and unowned ones
|
|
30
|
+
brief <TASK> [--owner <name>] [--base <rev>] brief for a task, with the base verify will use
|
|
31
|
+
(refuses another owner's task)
|
|
32
|
+
verify <TASK> [--claims "line"] [--base <rev>] [--fresh] run every check independently; prints corrective prompt
|
|
33
|
+
gate <TASK>|--all [--claims ...] [--base <rev>] [--fresh] release decision PASS/FAIL/BLOCKED + report (.proofline/reports)
|
|
34
|
+
--base: the revision the task started from (needed in a fresh clone
|
|
35
|
+
unless \`next\` pinned it or HEAD carries a Proofline-Base: trailer)
|
|
36
|
+
--fresh: run every check again, ignoring PASS results reused under
|
|
37
|
+
defaults.reuse_results
|
|
38
|
+
report <TASK> latest gate report
|
|
39
|
+
status [--owner <name>] task + phase status, evidence chain
|
|
40
|
+
evidence record <type> --data '{..}' | evidence verify | evidence log
|
|
41
|
+
hook pre-commit|pre-push (used by git hooks)
|
|
42
|
+
mcp MCP server on stdio
|
|
43
|
+
Global: --json --root <dir> Exit: 0 PASS, 1 FAIL, 2 BLOCKED, 3 usage`;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const [cmd, sub] = pos;
|
|
47
|
+
switch (cmd) {
|
|
48
|
+
case 'init': {
|
|
49
|
+
if (flags.approvals === true) throw new Error('--approvals needs a directory');
|
|
50
|
+
const r = P.init(root, { hooks: !flags['no-hooks'], approvals: flags.approvals });
|
|
51
|
+
print(r, `initialised ${root}${r.made.map((m) => `\n ${m}`).join('')}`);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case 'contract': {
|
|
55
|
+
if (sub === 'create') {
|
|
56
|
+
const spec = flags.from ? JSON.parse(fs.readFileSync(flags.from, 'utf8')) : { id: flags.id, title: flags.title, phase: flags.phase, owner: owner(), description: flags.description || '', depends_on: flags.depends ? String(flags.depends).split(',') : [], scope: flags.scope ? String(flags.scope).split(',') : undefined, criteria: [] };
|
|
57
|
+
const specs = Array.isArray(spec) ? spec : spec.tasks || [spec];
|
|
58
|
+
const out = specs.map((s) => P.contractCreate(root, s));
|
|
59
|
+
print(out, out.map((o) => `${o.task.id}: ${o.valid ? 'valid' : 'INCOMPLETE — ' + o.problems.join('; ')}`).join('\n'));
|
|
60
|
+
process.exitCode = out.every((o) => o.valid) ? 0 : 1;
|
|
61
|
+
} else if (sub === 'validate') { const e = P.C.validate(P.C.load(root)); print({ valid: !e.length, problems: e }, e.length ? e.join('\n') : 'contract valid'); process.exitCode = e.length ? 1 : 0; }
|
|
62
|
+
else if (sub === 'show') print(P.C.load(root));
|
|
63
|
+
else { console.error(HELP); process.exitCode = 3; }
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case 'next': { const n = P.taskNext(root, { owner: owner() }); print(n, n.brief || n.message); break; }
|
|
67
|
+
case 'brief': { const b = P.taskBrief(root, sub, { owner: owner(), base: base() }); print({ brief: b }, b); break; }
|
|
68
|
+
case 'verify': case 'gate': {
|
|
69
|
+
const c = P.C.load(root);
|
|
70
|
+
const ids = flags.all ? c.tasks.map((t) => t.id) : flags.ci ? (c.ci?.tasks || []) : [sub || P.activeTask(root)].filter(Boolean);
|
|
71
|
+
if (!ids.length) { if (flags.ci) { const e = P.C.validate(c); print({ decision: e.length ? 'FAIL' : 'PASS', problems: e }, e.length ? e.join('\n') : 'contract valid; no ci.tasks'); process.exitCode = e.length ? 1 : 0; break; } throw new Error('which task? (proofline next)'); }
|
|
72
|
+
const outs = ids.map((id) => P.E.gate(root, id, { claims: claims(), base: base(), fresh: flags.fresh === true, skipTypes: flags['skip-pushed'] || flags.ci ? ['pushed'] : [] }));
|
|
73
|
+
const worst = outs.some((o) => o.decision === 'BLOCKED') ? 'BLOCKED' : outs.some((o) => o.decision === 'FAIL') ? 'FAIL' : 'PASS';
|
|
74
|
+
print(outs.length === 1 ? outs[0] : { decision: worst, gates: outs.map((o) => ({ task: o.task, decision: o.decision, failing: o.failing, blocked: o.blocked })) },
|
|
75
|
+
outs.map((o) => `== ${o.task}: ${o.decision}\n base: ${o.base_ref} = ${o.base?.slice(0, 8) || 'not found'} (${o.base_source})\n${o.results.map((r) => ` ${r.status.toUpperCase().padEnd(5)} ${r.id} [${r.type}] ${r.detail.split('\n')[0]}`).join('\n')}${o.forbidden.length ? '\n forbidden: ' + o.forbidden.map((f) => `${f.rule}@${f.file}`).join(', ') : ''}\n report: ${o.report}\n\n${o.corrective_prompt}`).join('\n\n') + `\n\nDECISION: ${worst}`);
|
|
76
|
+
process.exitCode = code(worst);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case 'report': { const r = P.report(root, sub); print(r, r.markdown || r.error); process.exitCode = r.error ? 1 : 0; break; }
|
|
80
|
+
case 'status': { const s = P.status(root, { owner: owner() }); print(s, Object.entries(s.tasks).map(([k, v]) => `${k.padEnd(14)} ${v.status}`).join('\n') + '\n\n' + s.phases.map((p) => `${p.phase.padEnd(10)} ${p.verified}/${p.tasks}${p.open ? ' (open)' : ''}`).join('\n') + `\n\nevidence chain: ${s.chain.ok ? 'intact' : 'BROKEN'} (${s.chain.entries ?? '?'} entries); active: ${s.active || '-'}`); break; }
|
|
81
|
+
case 'evidence': {
|
|
82
|
+
if (sub === 'record') print(P.evidenceRecord(root, pos[2] || 'note', flags.data ? JSON.parse(flags.data) : {}));
|
|
83
|
+
else if (sub === 'verify') { const v = verifyChain(root); print(v, v.ok ? `chain intact: ${v.entries} entries, head ${v.head.slice(0, 16)}` : `CHAIN BROKEN at ${v.at}: ${v.reason}`); process.exitCode = v.ok ? 0 : 2; }
|
|
84
|
+
else if (sub === 'log') print(readLog(root));
|
|
85
|
+
else { console.error(HELP); process.exitCode = 3; }
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case 'hook': {
|
|
89
|
+
const r = sub === 'pre-commit' ? P.hookPreCommit(root) : sub === 'pre-push' ? P.hookPrePush(root) : null;
|
|
90
|
+
if (!r) { process.exitCode = 3; break; }
|
|
91
|
+
if (!r.ok) { console.error(`proofline ${sub}: REFUSED\n${r.msg}\n(There is no bypass. Fix the cause.)`); process.exitCode = 1; }
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case 'mcp': await import('../src/mcp.mjs').then((m) => m.serve(root)); break;
|
|
95
|
+
default: console.log(HELP); process.exitCode = cmd ? 3 : 0;
|
|
96
|
+
}
|
|
97
|
+
} catch (e) { console.error(`proofline: ${e.message}`); process.exitCode = 3; }
|
package/docs/00-index.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Documentation index
|
|
2
|
+
|
|
3
|
+
This is the source of truth for the Proofline design. Requirements are normative where marked **MUST**; roadmap dates and implementation details remain provisional until explicitly accepted.
|
|
4
|
+
|
|
5
|
+
## Product and design
|
|
6
|
+
|
|
7
|
+
- [01 — Scope and product brief](01-scope.md)
|
|
8
|
+
- [02 — Requirements](02-requirements.md)
|
|
9
|
+
- [03 — Domain model and terminology](03-domain-model.md)
|
|
10
|
+
- [04 — Architecture](04-architecture.md)
|
|
11
|
+
- [05 — Task lifecycle and state machine](05-lifecycle.md)
|
|
12
|
+
- [06 — Verification and reconciliation](06-verification.md)
|
|
13
|
+
- [07 — Evidence model](07-evidence.md)
|
|
14
|
+
- [08 — Agent adapters](08-adapters.md)
|
|
15
|
+
- [09 — Security and trust boundaries](09-security.md)
|
|
16
|
+
|
|
17
|
+
## Delivery and validation
|
|
18
|
+
|
|
19
|
+
- [10 — Test and conformance strategy](10-conformance.md)
|
|
20
|
+
- [11 — Development and release pipeline](11-development-pipeline.md)
|
|
21
|
+
- [12 — Roadmap](12-roadmap.md)
|
|
22
|
+
- [13 — Interdependencies](13-interdependencies.md)
|
|
23
|
+
- [14 — Metrics and evaluation](14-metrics.md)
|
|
24
|
+
|
|
25
|
+
## Decisions and examples
|
|
26
|
+
|
|
27
|
+
- [Architecture decision records](adr/)
|
|
28
|
+
- [Example task contract](../examples/demo-contract/proofline.json)
|
|
29
|
+
- [Change log](../CHANGELOG.md)
|
|
30
|
+
|
|
31
|
+
## Status vocabulary
|
|
32
|
+
|
|
33
|
+
| Status | Meaning |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `planned` | Defined but not implemented |
|
|
36
|
+
| `in_progress` | Active implementation; not yet accepted |
|
|
37
|
+
| `implemented` | Code exists; tests may still be incomplete |
|
|
38
|
+
| `verified` | Required checks passed and evidence is available for the identified revision |
|
|
39
|
+
| `blocked` | A mandatory gate or dependency prevents progress |
|
|
40
|
+
| `partial` | Some requirements are met; remaining gaps are explicit |
|
|
41
|
+
| `unverified` | Evidence is absent, stale, inconclusive, or insufficient |
|
|
42
|
+
|
|
43
|
+
A status label is not a substitute for evidence. In particular, `implemented` is not equivalent to `verified`.
|