pattern-mcp 0.3.0 → 0.5.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/README.md +466 -5
- package/dist/index.js +762 -14
- package/dist/telemetry.js +222 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ whether to:
|
|
|
32
32
|
|
|
33
33
|
Pattern is designed for agents to use **while they are building**.
|
|
34
34
|
|
|
35
|
-
It exposes
|
|
35
|
+
It exposes seven tools:
|
|
36
36
|
|
|
37
37
|
- `recommend_component` — evaluates a UI component need and returns a
|
|
38
38
|
structured recommendation.
|
|
@@ -44,7 +44,19 @@ It exposes four tools:
|
|
|
44
44
|
account.
|
|
45
45
|
- `read_ledger` — lists past `recommend_component` judgments for a
|
|
46
46
|
`project_id`, including any that were served from the ledger cache (see
|
|
47
|
-
[Per-project judgment ledger](#per-project-judgment-ledger))
|
|
47
|
+
[Per-project judgment ledger](#per-project-judgment-ledger)); pass
|
|
48
|
+
`feature_id` instead of browsing by keyword to get a full cost rollup for
|
|
49
|
+
one feature (see [Tool: `report_build_cost`](#tool-report_build_cost)).
|
|
50
|
+
- `report_build_cost` — self-reports the end-to-end build cost for one
|
|
51
|
+
feature, so cost incurred after Pattern's own verdict (the actual
|
|
52
|
+
scaffold/install/build) is still attributable back to it.
|
|
53
|
+
- `report_outcome_proxy` — self-reports a value signal (rework, time to
|
|
54
|
+
merge, kept-vs-replaced) for one feature, deliberately independent of
|
|
55
|
+
Pattern's own verdict -- see [Outcome
|
|
56
|
+
proxies](#outcome-proxies).
|
|
57
|
+
- `check_ledger_liveness` — checks whether a ledger entry's recorded
|
|
58
|
+
`file_path` still exists and still references its `chosen_candidate` --
|
|
59
|
+
see [Tool: `check_ledger_liveness`](#tool-check_ledger_liveness).
|
|
48
60
|
|
|
49
61
|
## How it works
|
|
50
62
|
|
|
@@ -409,6 +421,27 @@ Leave `checklist` out to keep today's default behavior: `recommend_component`
|
|
|
409
421
|
extracts its own checklist internally, exactly as before this option
|
|
410
422
|
existed.
|
|
411
423
|
|
|
424
|
+
#### `feature_id`
|
|
425
|
+
|
|
426
|
+
`feature_id` is optional -- a stable identifier for the feature this
|
|
427
|
+
component need belongs to (e.g. a ticket id or branch name). Its only use
|
|
428
|
+
is joining this call's cost with a later
|
|
429
|
+
[`report_build_cost`](#tool-report_build_cost) call for the same feature.
|
|
430
|
+
Omit it to have one derived deterministically from `project_id` +
|
|
431
|
+
`component_need`; only meaningful together with `project_id`. See
|
|
432
|
+
[Feature cost attribution](#feature-cost-attribution).
|
|
433
|
+
|
|
434
|
+
#### `file_path`
|
|
435
|
+
|
|
436
|
+
`file_path` is optional -- path (relative to `PROJECT_ROOT`) where this
|
|
437
|
+
component decision is expected to be implemented, if already known.
|
|
438
|
+
Usually not known yet at call time, since the decision typically precedes
|
|
439
|
+
the file existing. When provided, it's stored on the resulting ledger
|
|
440
|
+
entry and [`check_ledger_liveness`](#tool-check_ledger_liveness) can later
|
|
441
|
+
confirm the file still exists and still references `chosen_candidate`. It
|
|
442
|
+
cannot currently be attached to an entry after the fact -- see [Ledger
|
|
443
|
+
integrity and decision provenance](#ledger-integrity-and-decision-provenance).
|
|
444
|
+
|
|
412
445
|
**Is the checklist actually skipped, not just re-derived?** Checked, not
|
|
413
446
|
assumed. `breakdown_ms.extract` for a `checklist`-provided call is smaller
|
|
414
447
|
than the default path's, but not near-zero -- which raised the question of
|
|
@@ -667,6 +700,10 @@ came back with `served_from_ledger: true`.
|
|
|
667
700
|
match, no embeddings) against stored entries' `component_need`. Omit to
|
|
668
701
|
list everything for the project.
|
|
669
702
|
- `limit` is optional, defaults to 20. Most recent entries first.
|
|
703
|
+
- `feature_id` is optional. When provided, `component_need` and `limit`
|
|
704
|
+
are ignored and the response is a full cost rollup for that one feature
|
|
705
|
+
instead of a keyword listing -- see [Feature cost
|
|
706
|
+
attribution](#feature-cost-attribution).
|
|
670
707
|
|
|
671
708
|
### Output
|
|
672
709
|
|
|
@@ -678,6 +715,7 @@ came back with `served_from_ledger: true`.
|
|
|
678
715
|
"id": "a1b2c3d4-...",
|
|
679
716
|
"timestamp": "2026-08-29T19:50:47.073Z",
|
|
680
717
|
"project_id": "my-booking-app",
|
|
718
|
+
"feature_id": "3f9a21c0",
|
|
681
719
|
"component_need": "cancellation policy display with refund tiers by date",
|
|
682
720
|
"domain": "Airbnb-style rental marketplace",
|
|
683
721
|
"framework": "React + Tailwind",
|
|
@@ -691,24 +729,291 @@ came back with `served_from_ledger: true`.
|
|
|
691
729
|
"confidence": "low",
|
|
692
730
|
"reason": "scored",
|
|
693
731
|
"coverage": "5/8 (62.5%)",
|
|
694
|
-
"
|
|
732
|
+
"cost_usd": 0.087,
|
|
733
|
+
"cache_hit": false,
|
|
734
|
+
"project_conventions_snapshot": "9f3a1c7e2b0d4f5a",
|
|
735
|
+
"file_path": null,
|
|
736
|
+
"snapshot_ref": "a1b2c3d4e5f6...",
|
|
737
|
+
"last_verified_live": null,
|
|
738
|
+
"live_status": "unknown"
|
|
695
739
|
}
|
|
696
740
|
]
|
|
697
741
|
}
|
|
698
742
|
```
|
|
699
743
|
|
|
744
|
+
`file_path`/`snapshot_ref`/`last_verified_live`/`live_status` are the
|
|
745
|
+
ledger integrity + decision provenance fields -- see [Ledger integrity and
|
|
746
|
+
decision provenance](#ledger-integrity-and-decision-provenance) and [Tool:
|
|
747
|
+
`check_ledger_liveness`](#tool-check_ledger_liveness). Entries written
|
|
748
|
+
before this feature shipped read back with `file_path`/`snapshot_ref`/
|
|
749
|
+
`last_verified_live` as `null` and `live_status` as `"unknown"` rather
|
|
750
|
+
than missing keys.
|
|
751
|
+
|
|
752
|
+
Passing `feature_id` instead returns:
|
|
753
|
+
|
|
754
|
+
```json
|
|
755
|
+
{
|
|
756
|
+
"project_id": "my-booking-app",
|
|
757
|
+
"feature_id": "3f9a21c0",
|
|
758
|
+
"verdict_entries": [ "...same shape as above, filtered to this feature_id..." ],
|
|
759
|
+
"build_records": [
|
|
760
|
+
{ "id": "...", "timestamp": "...", "project_id": "my-booking-app", "feature_id": "3f9a21c0", "tokens_used": 9000, "cost_usd": 1.25, "outcome": "shipped" }
|
|
761
|
+
],
|
|
762
|
+
"total_cost_usd": 1.34,
|
|
763
|
+
"outcome_proxy": { "time_to_merge_hours": 3.5, "reworked": true, "days_to_rework": 12, "status_at_30d": "kept" },
|
|
764
|
+
"outcome_proxy_history": [ "...every raw report_outcome_proxy record for this feature_id, oldest first..." ]
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
`outcome_proxy` is `null` (and `outcome_proxy_history` an empty array)
|
|
769
|
+
when no `report_outcome_proxy` calls have been made for this feature yet
|
|
770
|
+
-- see [Outcome proxies](#outcome-proxies).
|
|
771
|
+
|
|
700
772
|
Each entry holds only distilled fields -- `candidates_evaluated` never
|
|
701
773
|
contains raw HTML, full prop tables, or the per-requirement evidence text
|
|
702
774
|
`recommend_component` itself returns. See
|
|
703
775
|
[Data minimization](#data-minimization) below.
|
|
704
776
|
|
|
777
|
+
## Tool: `report_build_cost`
|
|
778
|
+
|
|
779
|
+
Self-reports the end-to-end build cost for one feature. Pattern only ever
|
|
780
|
+
sees the cost of judging *what* to use (`recommend_component`'s own
|
|
781
|
+
`_meta.estimated_cost_usd`); everything past that -- the actual scaffold,
|
|
782
|
+
install, or custom build -- happens outside Pattern entirely and Pattern
|
|
783
|
+
has no way to observe it. Call this once, after the calling agent's build
|
|
784
|
+
for a feature is actually complete (shipped, abandoned, or replaced), not
|
|
785
|
+
on every verdict.
|
|
786
|
+
|
|
787
|
+
### Input
|
|
788
|
+
|
|
789
|
+
```json
|
|
790
|
+
{
|
|
791
|
+
"feature_id": "3f9a21c0",
|
|
792
|
+
"project_id": "my-booking-app",
|
|
793
|
+
"tokens_used": 9000,
|
|
794
|
+
"cost_usd": 1.25,
|
|
795
|
+
"outcome": "shipped"
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
- `feature_id` is required -- either a value you explicitly passed to an
|
|
800
|
+
earlier `recommend_component` call for this feature, or (if you didn't)
|
|
801
|
+
the same value `recommend_component` derives on its own:
|
|
802
|
+
`sha256(project_id + "::" + component_need, lowercased/trimmed)`
|
|
803
|
+
truncated to 8 hex characters. When in doubt, call `read_ledger` with
|
|
804
|
+
just `project_id` and copy the `feature_id` off the relevant entry
|
|
805
|
+
rather than re-deriving it by hand.
|
|
806
|
+
- `project_id` is optional but recommended -- without it, this record
|
|
807
|
+
still joins to a `recommend_component` entry by `feature_id` alone, but
|
|
808
|
+
`read_ledger`'s rollup can't scope it to one project.
|
|
809
|
+
- `tokens_used` is optional.
|
|
810
|
+
- `cost_usd` is required -- your own real number, not Pattern's.
|
|
811
|
+
- `outcome` is required: `"shipped"`, `"abandoned"`, or
|
|
812
|
+
`"replaced_with_existing"`.
|
|
813
|
+
|
|
814
|
+
### Output
|
|
815
|
+
|
|
816
|
+
```json
|
|
817
|
+
{
|
|
818
|
+
"status": "recorded",
|
|
819
|
+
"record": {
|
|
820
|
+
"id": "c5706b47-...",
|
|
821
|
+
"timestamp": "2026-09-02T01:25:29.653Z",
|
|
822
|
+
"project_id": "my-booking-app",
|
|
823
|
+
"feature_id": "3f9a21c0",
|
|
824
|
+
"tokens_used": 9000,
|
|
825
|
+
"cost_usd": 1.25,
|
|
826
|
+
"outcome": "shipped"
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
This only appends a local record to `~/.pattern/build_ledger.jsonl`
|
|
832
|
+
(override with `PATTERN_BUILD_LEDGER_PATH`) -- it never re-runs any
|
|
833
|
+
judgment and never calls the Anthropic API.
|
|
834
|
+
|
|
835
|
+
## Tool: `report_outcome_proxy`
|
|
836
|
+
|
|
837
|
+
Self-reports a value signal for one feature, deliberately independent of
|
|
838
|
+
Pattern's own verdict -- the whole point is a signal that could
|
|
839
|
+
*contradict* the verdict, so nothing on this path ever reads
|
|
840
|
+
`coverage_pct`, `confidence`, or any other Pattern-produced field. Compute
|
|
841
|
+
`reworked`/`days_to_rework` and `time_to_merge_hours` from your own repo's
|
|
842
|
+
real git history (e.g. `git log --follow` against the files this
|
|
843
|
+
feature's build touched) rather than relying on Pattern -- rework rate and
|
|
844
|
+
time-to-merge need real git *history*, a materially bigger surface than
|
|
845
|
+
the one narrow, read-only exception described in [Ledger integrity and
|
|
846
|
+
decision provenance](#ledger-integrity-and-decision-provenance) below.
|
|
847
|
+
Report `status_at_30d` only once a real ~30-day-post-merge horizon has
|
|
848
|
+
actually passed.
|
|
849
|
+
|
|
850
|
+
Safe to call more than once for the same `feature_id` as more signal
|
|
851
|
+
becomes available over time -- e.g. `time_to_merge_hours` right after
|
|
852
|
+
merge, `reworked` on a later re-check, `status_at_30d` at the 30-day mark.
|
|
853
|
+
`read_ledger`'s `feature_id` rollup merges every report into one
|
|
854
|
+
latest-value-per-field view (a later report only overwrites the specific
|
|
855
|
+
fields it includes, never the others).
|
|
856
|
+
|
|
857
|
+
### Input
|
|
858
|
+
|
|
859
|
+
```json
|
|
860
|
+
{
|
|
861
|
+
"feature_id": "3f9a21c0",
|
|
862
|
+
"project_id": "my-booking-app",
|
|
863
|
+
"reworked": true,
|
|
864
|
+
"days_to_rework": 12
|
|
865
|
+
}
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
- `feature_id` is required.
|
|
869
|
+
- `project_id` is optional but recommended, same reasoning as
|
|
870
|
+
`report_build_cost`.
|
|
871
|
+
- `reworked`, `days_to_rework`, `time_to_merge_hours`, `status_at_30d` are
|
|
872
|
+
all individually optional, but **at least one is required** -- an empty
|
|
873
|
+
report is rejected rather than silently recording nothing.
|
|
874
|
+
|
|
875
|
+
### Output
|
|
876
|
+
|
|
877
|
+
```json
|
|
878
|
+
{
|
|
879
|
+
"status": "recorded",
|
|
880
|
+
"record": {
|
|
881
|
+
"id": "8a2f1e0c-...",
|
|
882
|
+
"timestamp": "2026-09-16T18:04:12.881Z",
|
|
883
|
+
"project_id": "my-booking-app",
|
|
884
|
+
"feature_id": "3f9a21c0",
|
|
885
|
+
"reworked": true,
|
|
886
|
+
"days_to_rework": 12
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
This only appends a local record to `~/.pattern/outcome_proxies.jsonl`
|
|
892
|
+
(override with `PATTERN_OUTCOME_PROXY_PATH`) -- it never calls the
|
|
893
|
+
Anthropic API.
|
|
894
|
+
|
|
895
|
+
## Tool: `check_ledger_liveness`
|
|
896
|
+
|
|
897
|
+
Checks whether ledger entries for a `project_id` are still **live** --
|
|
898
|
+
does the `file_path` recorded on the entry (if any, see
|
|
899
|
+
[`file_path`](#tool-recommend_component)) still exist, and does it still
|
|
900
|
+
mention `chosen_candidate`. See [Ledger integrity and decision
|
|
901
|
+
provenance](#ledger-integrity-and-decision-provenance) for the full design
|
|
902
|
+
and its deliberate limits.
|
|
903
|
+
|
|
904
|
+
This is the **one exception** to Pattern otherwise having no filesystem
|
|
905
|
+
access to your repo (see [Outcome proxies](#outcome-proxies) above) --
|
|
906
|
+
scoped narrowly to read-only `fs.existsSync`/file-read calls against
|
|
907
|
+
`PROJECT_ROOT` (defaults to this server's own working directory; override
|
|
908
|
+
with `PATTERN_PROJECT_ROOT`). It never writes to your repo and never runs
|
|
909
|
+
an arbitrary shell command.
|
|
910
|
+
|
|
911
|
+
### Input
|
|
912
|
+
|
|
913
|
+
```json
|
|
914
|
+
{
|
|
915
|
+
"project_id": "my-booking-app",
|
|
916
|
+
"ledger_entry_id": "a1b2c3d4-..."
|
|
917
|
+
}
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
- `project_id` is required.
|
|
921
|
+
- `ledger_entry_id` is optional -- check just that one entry instead of
|
|
922
|
+
every entry for `project_id` that has a `file_path` set.
|
|
923
|
+
|
|
924
|
+
### Output
|
|
925
|
+
|
|
926
|
+
```json
|
|
927
|
+
{
|
|
928
|
+
"project_id": "my-booking-app",
|
|
929
|
+
"checked": 1,
|
|
930
|
+
"total_entries": 2,
|
|
931
|
+
"results": [
|
|
932
|
+
{
|
|
933
|
+
"ledger_entry_id": "a1b2c3d4-...",
|
|
934
|
+
"component_need": "cancellation policy display with refund tiers by date",
|
|
935
|
+
"file_path": "src/components/CancellationPolicy.tsx",
|
|
936
|
+
"live_status": "live",
|
|
937
|
+
"checked_at": "2026-09-02T20:11:03.442Z",
|
|
938
|
+
"note": null
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
"ledger_entry_id": "e5f6a7b8-...",
|
|
942
|
+
"component_need": "gallery",
|
|
943
|
+
"file_path": null,
|
|
944
|
+
"live_status": "unknown",
|
|
945
|
+
"checked_at": null,
|
|
946
|
+
"note": "no file_path recorded on this entry -- nothing to check"
|
|
947
|
+
}
|
|
948
|
+
]
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
`live_status` is one of `"live"`, `"orphaned"`, `"unknown"`, or
|
|
953
|
+
(reserved, not yet produced -- see [Ledger integrity and decision
|
|
954
|
+
provenance](#ledger-integrity-and-decision-provenance)) `"dangling"`.
|
|
955
|
+
Entries with no `file_path` are listed but never checked or written to
|
|
956
|
+
`ledger_liveness.jsonl` -- their status is permanently `"unknown"` since
|
|
957
|
+
there's nothing to check. Results here are also layered onto
|
|
958
|
+
`read_ledger`'s `live_status`/`last_verified_live` fields for the same
|
|
959
|
+
entries afterward -- `check_ledger_liveness` is the only thing that
|
|
960
|
+
advances those fields past their write-time defaults.
|
|
961
|
+
|
|
962
|
+
## Feature cost attribution
|
|
963
|
+
|
|
964
|
+
Every `recommend_component` call that writes to the ledger -- a fresh
|
|
965
|
+
judgment *or* a $0 [ledger cache hit](#the-cache-hit-exception) -- now
|
|
966
|
+
carries a `feature_id`, plus its own `cost_usd` and `cache_hit`. Pair that
|
|
967
|
+
with `report_build_cost`'s build-time record and `read_ledger`'s
|
|
968
|
+
`feature_id` rollup, and total spend on a feature (judgment + build,
|
|
969
|
+
across however many calls) is queryable end to end, not just the cost of
|
|
970
|
+
one verdict call.
|
|
971
|
+
|
|
972
|
+
`feature_id` defaults to a deterministic derivation --
|
|
973
|
+
`sha256(project_id + "::" + component_need)` truncated to 8 hex chars --
|
|
974
|
+
so repeat calls for the same feature land under the same id automatically,
|
|
975
|
+
with no coordination needed between `recommend_component` and
|
|
976
|
+
`report_build_cost` calls. Pass your own `feature_id` explicitly (e.g. a
|
|
977
|
+
ticket id or branch name) if you'd rather key on something stable on your
|
|
978
|
+
own side.
|
|
979
|
+
|
|
980
|
+
## Outcome proxies
|
|
981
|
+
|
|
982
|
+
Cost data alone (`feature cost attribution` above) can't answer whether a
|
|
983
|
+
cheaper build was actually *worth it* -- comparing it against Pattern's
|
|
984
|
+
own verdict/`coverage_pct` would be circular, since that's the very thing
|
|
985
|
+
being evaluated. `report_outcome_proxy` attaches a cheap, non-circular
|
|
986
|
+
value signal per `feature_id` instead:
|
|
987
|
+
|
|
988
|
+
- **`reworked` / `days_to_rework`** (primary proxy) -- was any file this
|
|
989
|
+
feature's build touched modified again after the original merge, and if
|
|
990
|
+
so, how soon? Computed from real git history, not Pattern's own data.
|
|
991
|
+
- **`time_to_merge_hours`** (secondary proxy) -- how long the feature
|
|
992
|
+
took from first commit to merge.
|
|
993
|
+
- **`status_at_30d`** (tertiary, longer-horizon proxy) -- at a ~30-day
|
|
994
|
+
horizon, does the component Pattern recommended still exist in the
|
|
995
|
+
codebase, unchanged in kind (`"kept"`), was it swapped for a different
|
|
996
|
+
approach (`"replaced"`), or removed entirely (`"removed"`)?
|
|
997
|
+
|
|
998
|
+
`read_ledger`'s `feature_id` rollup returns both `outcome_proxy` (the
|
|
999
|
+
merged latest-value-per-field view) and `outcome_proxy_history` (every
|
|
1000
|
+
raw report, in case the timeline itself matters) alongside the cost
|
|
1001
|
+
figures from [Feature cost attribution](#feature-cost-attribution) above
|
|
1002
|
+
-- so "what did this feature cost end to end, and did it hold up?" is
|
|
1003
|
+
answerable from one `read_ledger` call.
|
|
1004
|
+
|
|
705
1005
|
## Per-project judgment ledger
|
|
706
1006
|
|
|
707
1007
|
Distinct from [per-project decision memory](#per-project-decision-memory)
|
|
708
1008
|
below -- that file only gains an entry when `record_component_decision` is
|
|
709
1009
|
explicitly called. The ledger instead gains one entry automatically for
|
|
710
|
-
**every** `recommend_component` call
|
|
711
|
-
|
|
1010
|
+
**every** `recommend_component` call with a `project_id` that lands on
|
|
1011
|
+
reason `"scored"` or `"no_candidates_found"` -- whether that's a fresh
|
|
1012
|
+
call that reached the API, or a $0 [ledger cache
|
|
1013
|
+
hit](#the-cache-hit-exception) served without one (`cache_hit: true`,
|
|
1014
|
+
`cost_usd: 0`), so a feature's total cost still rolls up correctly even
|
|
1015
|
+
once most of its later calls are free. See [Feature cost
|
|
1016
|
+
attribution](#feature-cost-attribution).
|
|
712
1017
|
|
|
713
1018
|
Pattern stores it locally in:
|
|
714
1019
|
|
|
@@ -769,6 +1074,96 @@ convention: a raw or extended object throws rather than silently
|
|
|
769
1074
|
persisting. Run `node scripts/verify-ledger-boundary.mjs` (after
|
|
770
1075
|
`npm run build`) to check this boundary directly.
|
|
771
1076
|
|
|
1077
|
+
## Ledger integrity and decision provenance
|
|
1078
|
+
|
|
1079
|
+
Two gaps in the ledger, surfaced from user feedback: it tracks that a
|
|
1080
|
+
decision was made, but not whether the thing it decided about is still
|
|
1081
|
+
live in your codebase, and it stores the checklist/verdict but not a
|
|
1082
|
+
version pin or an exportable artifact you can attach to a PR or issue.
|
|
1083
|
+
This section covers what's shipped so far -- **P0/P1 of both halves**, not
|
|
1084
|
+
the full spec. See `pattern-ledger-integrity-and-provenance-spec.md` for
|
|
1085
|
+
the complete phased plan; P2/P3 (a scheduled/batch sweep, dangling-cluster
|
|
1086
|
+
detection, the provenance-artifact exporter, and GitHub PR/issue posting)
|
|
1087
|
+
are not built yet.
|
|
1088
|
+
|
|
1089
|
+
**This is the one deliberate exception** to Pattern otherwise having [no
|
|
1090
|
+
filesystem/git access to your repo](#per-project-judgment-ledger) at all
|
|
1091
|
+
(the principle `report_build_cost`/`report_outcome_proxy` are built
|
|
1092
|
+
around). It's narrow on purpose:
|
|
1093
|
+
|
|
1094
|
+
- `git rev-parse HEAD` (read-only, never touches repo state) to capture
|
|
1095
|
+
`snapshot_ref` on every ledger write.
|
|
1096
|
+
- `fs.existsSync` plus a plain-text read of one file, only for a
|
|
1097
|
+
`file_path` you explicitly passed to `recommend_component`, only inside
|
|
1098
|
+
`PROJECT_ROOT` (see below), to answer `check_ledger_liveness`.
|
|
1099
|
+
|
|
1100
|
+
Nothing here runs an arbitrary git or shell command, and nothing writes to
|
|
1101
|
+
your repo.
|
|
1102
|
+
|
|
1103
|
+
### `PROJECT_ROOT`
|
|
1104
|
+
|
|
1105
|
+
Defaults to `process.cwd()` -- for a locally-run stdio MCP server, that's
|
|
1106
|
+
normally the consuming repo's root, since MCP hosts typically launch the
|
|
1107
|
+
server with the project directory as its working directory. Override with
|
|
1108
|
+
`PATTERN_PROJECT_ROOT` if that assumption doesn't hold for your setup.
|
|
1109
|
+
|
|
1110
|
+
A `file_path` that's absolute or escapes `PROJECT_ROOT` via `../` resolves
|
|
1111
|
+
to `live_status: "unknown"` rather than being read -- belt-and-suspenders,
|
|
1112
|
+
since the calling agent already has real filesystem access to its own
|
|
1113
|
+
machine regardless.
|
|
1114
|
+
|
|
1115
|
+
### Decision provenance: `snapshot_ref`
|
|
1116
|
+
|
|
1117
|
+
Every ledger entry -- fresh judgment or [ledger cache
|
|
1118
|
+
hit](#the-cache-hit-exception) -- now carries `snapshot_ref`: the commit
|
|
1119
|
+
SHA of `PROJECT_ROOT` at the moment that line was written, or `null` when
|
|
1120
|
+
`PROJECT_ROOT` isn't a git repo (or `git` isn't installed, or the call
|
|
1121
|
+
times out) -- this never fails the underlying `recommend_component` call.
|
|
1122
|
+
Entries written before this shipped read back with `snapshot_ref: null`.
|
|
1123
|
+
|
|
1124
|
+
A cache-hit entry's `snapshot_ref` reflects the codebase state *when that
|
|
1125
|
+
cache-hit line was written*, not the original judgment's -- to see the
|
|
1126
|
+
original judgment's snapshot, look up the entry named in its
|
|
1127
|
+
`ledger_entry_id`/`original_verdict_timestamp` fields instead.
|
|
1128
|
+
|
|
1129
|
+
Not yet built (P1-P3 of Feature 2): a markdown export of one entry's full
|
|
1130
|
+
record for attaching to a PR/issue, and the GitHub posting action itself.
|
|
1131
|
+
|
|
1132
|
+
### Referential integrity: `file_path` / `live_status`
|
|
1133
|
+
|
|
1134
|
+
`recommend_component` optionally accepts `file_path` (see [Tool:
|
|
1135
|
+
`recommend_component`](#tool-recommend_component)) -- usually not known at
|
|
1136
|
+
call time, since the decision typically precedes the file existing. When
|
|
1137
|
+
set, [`check_ledger_liveness`](#tool-check_ledger_liveness) can later
|
|
1138
|
+
check whether that file still exists and still mentions
|
|
1139
|
+
`chosen_candidate`:
|
|
1140
|
+
|
|
1141
|
+
- **`live`** -- the file exists and mentions `chosen_candidate`.
|
|
1142
|
+
- **`orphaned`** -- `file_path` is set but the file no longer exists.
|
|
1143
|
+
- **`unknown`** -- no `file_path` was ever recorded, the path escapes
|
|
1144
|
+
`PROJECT_ROOT`, or the file exists but `chosen_candidate` can't be
|
|
1145
|
+
confirmed in it. Deliberately the default outcome for anything
|
|
1146
|
+
ambiguous: a false `"orphaned"` is worse than a lingering `"unknown"`.
|
|
1147
|
+
- **`dangling`** -- reserved, not yet produced. Feature 1's second
|
|
1148
|
+
staleness type (a cluster of entries that only reference each other,
|
|
1149
|
+
with no live anchor anywhere) is graph-level analysis across the whole
|
|
1150
|
+
ledger, not a single-entry check -- P3, not built here.
|
|
1151
|
+
|
|
1152
|
+
Checks are on-demand only right now (call `check_ledger_liveness`
|
|
1153
|
+
yourself, or on whatever schedule you want) -- there's no automatic
|
|
1154
|
+
sweep. `live_status`/`last_verified_live` start `"unknown"`/`null` on
|
|
1155
|
+
every entry at write time and only ever advance via a
|
|
1156
|
+
`check_ledger_liveness` call; results are stored append-only in
|
|
1157
|
+
`~/.pattern/ledger_liveness.jsonl` (override with
|
|
1158
|
+
`PATTERN_LEDGER_LIVENESS_PATH`, same "append, never mutate the source
|
|
1159
|
+
line, most recent record wins at read time" convention as
|
|
1160
|
+
`outcome_proxies.jsonl`, see [Outcome proxies](#outcome-proxies)) and
|
|
1161
|
+
layered onto `ledger.jsonl`'s own entries at read time -- the ledger line
|
|
1162
|
+
itself is never rewritten.
|
|
1163
|
+
|
|
1164
|
+
Not yet built (P2-P3 of Feature 1): a scheduled/batch sweep across an
|
|
1165
|
+
entire ledger, and dangling-cluster detection.
|
|
1166
|
+
|
|
772
1167
|
## Per-project decision memory
|
|
773
1168
|
|
|
774
1169
|
Pattern stores confirmed decisions locally in:
|
|
@@ -837,9 +1232,75 @@ recommendations.
|
|
|
837
1232
|
Local project memory and the local call log are stored on the machine
|
|
838
1233
|
running Pattern. They are not sent anywhere by Pattern itself.
|
|
839
1234
|
|
|
1235
|
+
The one exception is opt-in telemetry, off by default -- see
|
|
1236
|
+
[Telemetry](#telemetry) below for exactly what it sends and how to turn
|
|
1237
|
+
it on or off.
|
|
1238
|
+
|
|
840
1239
|
Review [SECURITY.md](./SECURITY.md) before putting sensitive information
|
|
841
1240
|
into fields such as `component_need`, `domain`, or project IDs.
|
|
842
1241
|
|
|
1242
|
+
## Telemetry
|
|
1243
|
+
|
|
1244
|
+
Off by default. Nothing is sent anywhere for telemetry purposes unless
|
|
1245
|
+
you explicitly set:
|
|
1246
|
+
|
|
1247
|
+
```
|
|
1248
|
+
PATTERN_TELEMETRY=1
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
**The one-time notice.** The first time you run this version of Pattern
|
|
1252
|
+
-- whether it's a brand-new install or an upgrade from a version before
|
|
1253
|
+
telemetry existed -- it prints a short notice to stderr explaining all of
|
|
1254
|
+
this and how to opt in. It prints exactly once, ever (tracked by a marker
|
|
1255
|
+
file at `~/.pattern/telemetry_notice_shown`), then never again, regardless
|
|
1256
|
+
of whether you act on it. There's no interactive y/n prompt: Pattern's
|
|
1257
|
+
stdin is the MCP JSON-RPC channel the client uses to talk to it, so
|
|
1258
|
+
blocking on stdin for a keypress would fight the protocol handshake
|
|
1259
|
+
instead of showing a dialog -- a stderr notice is the safe equivalent for
|
|
1260
|
+
a stdio MCP server.
|
|
1261
|
+
|
|
1262
|
+
**Why it exists.** Two things about real usage can't be answered from
|
|
1263
|
+
this repo alone: whether people actually come back and use Pattern on a
|
|
1264
|
+
second or third project on their own, and how often a BYO Anthropic key
|
|
1265
|
+
actually hits a rate limit or runs out of credit in real sessions, not
|
|
1266
|
+
just the one time that happened during manual testing (see
|
|
1267
|
+
[Known limitations](#known-limitations)). Telemetry answers both without
|
|
1268
|
+
requiring anyone to fill out a survey.
|
|
1269
|
+
|
|
1270
|
+
**What gets sent, when enabled:**
|
|
1271
|
+
|
|
1272
|
+
- An anonymous, randomly generated install ID -- a UUID created once and
|
|
1273
|
+
stored at `~/.pattern/install_id` (overridable via
|
|
1274
|
+
`PATTERN_INSTALL_ID_PATH`), never derived from your machine, username,
|
|
1275
|
+
or any other identifying information. This is the only thing that ties
|
|
1276
|
+
two events together as "the same install."
|
|
1277
|
+
- A one-way SHA-256 hash of `project_id`, truncated to 16 hex characters
|
|
1278
|
+
-- never the raw `project_id` string. The hash lets Pattern count how
|
|
1279
|
+
many *distinct* projects one install has used, without ever seeing what
|
|
1280
|
+
those projects are named.
|
|
1281
|
+
- On every `recommend_component` call that reaches the API or the ledger
|
|
1282
|
+
cache-hit shortcut: `verdict`, `confidence`, `reason`,
|
|
1283
|
+
`ensemble_triggered`, `estimated_cost_usd`, and `served_from_ledger` --
|
|
1284
|
+
the same distilled shape already written to the
|
|
1285
|
+
[local call log](#local-call-log), not new information.
|
|
1286
|
+
- On a failed Anthropic API call specifically: the HTTP status code and a
|
|
1287
|
+
coarse classification (`rate_limit`, `insufficient_credit`, or `other`)
|
|
1288
|
+
-- never the request or response body.
|
|
1289
|
+
|
|
1290
|
+
**What never gets sent, telemetry on or off:** `component_need`,
|
|
1291
|
+
`domain`, `framework`, `existing_stack`, `requirements_checked` evidence,
|
|
1292
|
+
the raw `project_id`, or your Anthropic API key.
|
|
1293
|
+
|
|
1294
|
+
**Where it goes.** Events go to Pattern's PostHog project via its public,
|
|
1295
|
+
write-only project key (safe to ship in source -- it can send events, it
|
|
1296
|
+
cannot read data back). Set `PATTERN_POSTHOG_KEY` /
|
|
1297
|
+
`PATTERN_POSTHOG_HOST` to point at a different project, e.g. for
|
|
1298
|
+
self-hosting.
|
|
1299
|
+
|
|
1300
|
+
**Turning it off** is the default -- just don't set `PATTERN_TELEMETRY`.
|
|
1301
|
+
If you'd previously enabled it, unset the variable (or set it to `0`) to
|
|
1302
|
+
go back to fully local.
|
|
1303
|
+
|
|
843
1304
|
## Cost
|
|
844
1305
|
|
|
845
1306
|
Pattern uses the Anthropic API, so `recommend_component` has a cost.
|