@zalom/plastic 1.0.0-beta.3 → 1.0.0-beta.31

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 (102) hide show
  1. package/PLASTIC.md +293 -7
  2. package/README.md +10 -0
  3. package/agents/plastic-brainstorming.md +2 -2
  4. package/agents/plastic-enforcer.md +17 -2
  5. package/agents/plastic-executor.md +3 -3
  6. package/agents/plastic-future-intent-researcher.md +2 -1
  7. package/agents/plastic-intent-curator.md +15 -7
  8. package/agents/plastic-intent-discovery.md +37 -0
  9. package/agents/plastic-planner.md +2 -2
  10. package/agents/plastic-spec-specialist.md +2 -2
  11. package/hooks/code-gate +10 -1
  12. package/hooks/hooks.json +28 -3
  13. package/hooks/lock-gate +21 -0
  14. package/hooks/retrieval-gate +10 -0
  15. package/hooks/savepoint-pre +10 -0
  16. package/hooks/statusline +150 -41
  17. package/package.json +1 -1
  18. package/scripts/agent-report +21 -0
  19. package/scripts/doctor.rb +404 -5
  20. package/scripts/hook-auto-arm +1 -1
  21. package/scripts/hook-bash-gate +22 -4
  22. package/scripts/hook-code-gate +11 -6
  23. package/scripts/hook-create-gate +45 -9
  24. package/scripts/hook-gate-check +24 -23
  25. package/scripts/hook-lock-gate +83 -0
  26. package/scripts/hook-retrieval-gate +148 -0
  27. package/scripts/hook-savepoint-pre +32 -0
  28. package/scripts/hook-session-start +1 -1
  29. package/scripts/insight-append +51 -0
  30. package/scripts/lib/agent_models.rb +43 -0
  31. package/scripts/lib/bridge.rb +625 -47
  32. package/scripts/lib/frontmatter_writer.rb +130 -0
  33. package/scripts/lib/graph_rebuild.rb +328 -0
  34. package/scripts/lib/hook_registry.rb +95 -0
  35. package/scripts/lib/insights.rb +86 -0
  36. package/scripts/lib/installer_core.rb +64 -54
  37. package/scripts/lib/link_suggestions.rb +319 -0
  38. package/scripts/lib/links_projection.rb +160 -0
  39. package/scripts/lib/links_section.rb +207 -0
  40. package/scripts/lib/lock.rb +375 -0
  41. package/scripts/lib/power_tools.rb +75 -0
  42. package/scripts/lib/qmd_hook.rb +38 -25
  43. package/scripts/lib/qmd_sync.rb +36 -0
  44. package/scripts/lib/retrieval_gate.rb +211 -0
  45. package/scripts/lib/worktree.rb +384 -0
  46. package/scripts/link-suggest +213 -0
  47. package/scripts/new-intent +138 -29
  48. package/scripts/plastic-lock +164 -0
  49. package/scripts/project-links +287 -0
  50. package/scripts/qmd-sync +50 -3
  51. package/scripts/read-config +4 -0
  52. package/scripts/rebuild-graph +244 -0
  53. package/scripts/spawn-preamble +11 -3
  54. package/skills/auto/SKILL.md +98 -16
  55. package/skills/auto/evals/evals.json +48 -0
  56. package/skills/auto/references/agent-architecture.md +7 -4
  57. package/skills/auto/references/agent-report-contract.md +36 -1
  58. package/skills/brainstorming/SKILL.md +1 -0
  59. package/skills/brainstorming/evals/evals.json +22 -0
  60. package/skills/continuing/SKILL.md +30 -8
  61. package/skills/continuing/evals/evals.json +9 -0
  62. package/skills/creating-intent/SKILL.md +16 -2
  63. package/skills/creating-intent/evals/evals.json +16 -0
  64. package/skills/creating-intent/references/lifecycle.md +9 -4
  65. package/skills/creating-skills/SKILL.md +65 -0
  66. package/skills/creating-skills/evals/evals.json +108 -0
  67. package/skills/creating-skills/references/agents.md +168 -0
  68. package/skills/creating-skills/references/evals.md +41 -0
  69. package/skills/creating-skills/references/hooks.md +248 -0
  70. package/skills/creating-skills/references/progressive-disclosure.md +176 -0
  71. package/skills/creating-skills/references/scripts.md +166 -0
  72. package/skills/creating-skills/references/skills.md +165 -0
  73. package/skills/creating-skills/scripts/scaffold.rb +313 -0
  74. package/skills/dashboard/SKILL.md +5 -0
  75. package/skills/dashboard/evals/evals.json +22 -0
  76. package/skills/doctor/SKILL.md +4 -1
  77. package/skills/executing-plan/SKILL.md +4 -4
  78. package/skills/humanizer/SKILL.md +39 -0
  79. package/skills/humanizer/evals/evals.json +70 -0
  80. package/skills/humanizer/references/always-on-snippet.md +9 -0
  81. package/skills/humanizer/references/examples.md +48 -0
  82. package/skills/intent-curator/SKILL.md +7 -1
  83. package/skills/intent-curator/evals/evals.json +22 -0
  84. package/skills/intent-discovery/SKILL.md +46 -0
  85. package/skills/intent-starting/SKILL.md +127 -0
  86. package/skills/intent-starting/evals/evals.json +117 -0
  87. package/skills/intent-starting/references/boarding-matrix.md +35 -0
  88. package/skills/linking-intents/SKILL.md +54 -12
  89. package/skills/linking-intents/evals/evals.json +22 -0
  90. package/skills/linking-intents/references/zettelkasten.md +7 -0
  91. package/skills/lock/SKILL.md +41 -0
  92. package/skills/managing-index/SKILL.md +12 -0
  93. package/skills/managing-index/evals/evals.json +22 -0
  94. package/skills/managing-index/references/zettelkasten-linking.md +6 -1
  95. package/skills/releasing/SKILL.md +45 -1
  96. package/skills/research/SKILL.md +8 -0
  97. package/skills/research/evals/evals.json +22 -0
  98. package/templates/config.yml +8 -0
  99. package/templates/outcome.md +3 -0
  100. package/templates/revisions.md +58 -0
  101. package/skills/writing-instructions/SKILL.md +0 -159
  102. package/skills/writing-instructions/references/agentskills-spec.md +0 -135
package/hooks/hooks.json CHANGED
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "PreCompact": [
21
21
  {
22
- "matcher": "manual|auto",
22
+ "matcher": "",
23
23
  "hooks": [
24
24
  {
25
25
  "type": "command",
@@ -31,17 +31,32 @@
31
31
  ],
32
32
  "PreToolUse": [
33
33
  {
34
- "matcher": "Write|Edit|NotebookEdit",
34
+ "matcher": "Write|Edit|NotebookEdit|mcp__serena__replace_content|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol|mcp__serena__safe_delete_symbol|mcp__serena__rename_symbol",
35
35
  "hooks": [
36
36
  {
37
37
  "type": "command",
38
38
  "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook\" code-gate",
39
39
  "statusMessage": "Checking lifecycle gate..."
40
+ },
41
+ {
42
+ "type": "command",
43
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook\" lock-gate",
44
+ "statusMessage": "Checking lock gate..."
40
45
  }
41
46
  ]
42
47
  },
43
48
  {
44
- "matcher": "Write",
49
+ "matcher": "Write|Edit",
50
+ "hooks": [
51
+ {
52
+ "type": "command",
53
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook\" savepoint-pre",
54
+ "statusMessage": "Recording stage start..."
55
+ }
56
+ ]
57
+ },
58
+ {
59
+ "matcher": "Write|Edit|mcp__serena__replace_content|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol|mcp__serena__safe_delete_symbol|mcp__serena__rename_symbol",
45
60
  "hooks": [
46
61
  {
47
62
  "type": "command",
@@ -59,6 +74,16 @@
59
74
  "statusMessage": "Checking lifecycle gate..."
60
75
  }
61
76
  ]
77
+ },
78
+ {
79
+ "matcher": "Bash|Read|Grep|Glob",
80
+ "hooks": [
81
+ {
82
+ "type": "command",
83
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook\" retrieval-gate",
84
+ "statusMessage": "Checking retrieval gate..."
85
+ }
86
+ ]
62
87
  }
63
88
  ],
64
89
  "PostToolUse": [
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ INPUT=$(cat)
3
+ FILE_PATH=$(echo "$INPUT" | ruby -rjson -e '
4
+ data = JSON.parse(STDIN.read)
5
+ ti = data["tool_input"] || data["tool_params"] || {}
6
+ path = ti["file_path"] || ti["notebook_path"] || ti["relative_path"] || ""
7
+ if !path.empty? && !path.start_with?("/")
8
+ root = ti["project_root"] || data["cwd"] || ""
9
+ path = File.join(root, path) unless root.empty?
10
+ end
11
+ puts path
12
+ ' 2>/dev/null)
13
+
14
+ if [ -z "$FILE_PATH" ]; then
15
+ exit 0
16
+ fi
17
+
18
+ SESSION_ID=$(echo "$INPUT" | ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("session_id") || ""' 2>/dev/null)
19
+
20
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
21
+ ruby "$SCRIPT_DIR/../scripts/hook-lock-gate" "$FILE_PATH" "$SESSION_ID"
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # Retrieval-gate hook launcher (intent 84, Lever 2). No-op when there is no
3
+ # global store. Passes the PreToolUse JSON through on stdin (exec inherits it)
4
+ # and the plastic_home as ARGV[0], mirroring the qmd-search launcher.
5
+ GLOBAL_INDEX="$HOME/.plastic/INDEX.md"
6
+ if [ ! -f "$GLOBAL_INDEX" ]; then
7
+ exit 0
8
+ fi
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ exec ruby "$SCRIPT_DIR/../scripts/hook-retrieval-gate" "$HOME/.plastic"
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ INPUT=$(cat)
3
+ FILE_PATH=$(echo "$INPUT" | ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("tool_params", "file_path") || data.dig("tool_input", "file_path") || ""' 2>/dev/null)
4
+
5
+ if [ -z "$FILE_PATH" ]; then
6
+ exit 0
7
+ fi
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ ruby "$SCRIPT_DIR/../scripts/hook-savepoint-pre" "$FILE_PATH"
package/hooks/statusline CHANGED
@@ -1,56 +1,165 @@
1
1
  #!/bin/bash
2
- # plastic-hook-version: 2.0.0
3
- # StatusLine hook chains user's original statusline, adds Plastic info
2
+ # plastic-hook-version: 3.0.0
3
+ # StatusLine hook - Plastic owns the full statusline (no chaining).
4
+ # Renders: {model} - Plastic {version}[ -> {next}] - {path} - [{project}: {id} {title}]
5
+ # Pure bash (macOS 3.2 compatible). No ruby, no jq. Reads stdin once; small file reads only.
4
6
 
5
7
  INPUT=$(cat)
6
8
 
7
- # --- Chain original statusline ---
8
- ORIGINAL_CONFIG="$HOME/.plastic/.cache/original-statusline.json"
9
- ORIGINAL_OUTPUT=""
10
- if [ -f "$ORIGINAL_CONFIG" ]; then
11
- ORIGINAL_CMD=$(grep -o '"command":"[^"]*"' "$ORIGINAL_CONFIG" 2>/dev/null | head -1 | cut -d'"' -f4)
12
- if [ -z "$ORIGINAL_CMD" ]; then
13
- ORIGINAL_CMD=$(grep -o '"command": "[^"]*"' "$ORIGINAL_CONFIG" 2>/dev/null | head -1 | sed 's/.*"command": "//;s/".*//')
14
- fi
15
- if [ -n "$ORIGINAL_CMD" ] && [ -x "$ORIGINAL_CMD" ]; then
16
- ORIGINAL_OUTPUT=$(echo "$INPUT" | "$ORIGINAL_CMD" 2>/dev/null)
9
+ # --- Colors (ANSI-C quoting; bash 3.2 supports $'\033') ---
10
+ C_MODEL=$'\033[38;2;90;140;220m' # navy blue - model (lightened for legibility)
11
+ C_VER=$'\033[37m' # white - "Plastic {version}" and update arrow
12
+ C_NEXT=$'\033[33m' # yellow - next version
13
+ C_PATH=$'\033[38;2;128;134;144m' # gray - path (matches dim UI text)
14
+ C_BRACK=$'\033[33m' # yellow - work-unit brackets
15
+ C_PROJ=$'\033[38;2;249;140;30m' # orange - project name
16
+ C_ID=$'\033[38;2;45;212;191m' # teal - intent id (accent between orange + yellow)
17
+ C_INTENT=$'\033[33m' # yellow - intent title
18
+ C_SEP=$'\033[38;2;128;134;144m' # gray - separators
19
+ RST=$'\033[0m'
20
+ SEP="${C_SEP} "$'\302\267'" ${RST}" # " . " middot, dim
21
+
22
+ # --- Extract a flat JSON string field from stdin: json_str <key> ---
23
+ json_str() {
24
+ printf '%s' "$INPUT" \
25
+ | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" \
26
+ | head -1 \
27
+ | sed "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"//;s/\"$//"
28
+ }
29
+
30
+ # --- Model (model.display_name) ---
31
+ MODEL=$(json_str "display_name")
32
+
33
+ # --- Path (workspace.current_dir, fallback top-level cwd) ---
34
+ REAL_CWD=$(json_str "current_dir")
35
+ [ -z "$REAL_CWD" ] && REAL_CWD=$(json_str "cwd")
36
+ # Abbreviate $HOME -> ~
37
+ CWD="$REAL_CWD"
38
+ case "$CWD" in
39
+ "$HOME") CWD="~" ;;
40
+ "$HOME"/*) CWD="~${CWD#"$HOME"}" ;;
41
+ esac
42
+
43
+ # --- Plastic version ---
44
+ VERSION=""
45
+ [ -f "$HOME/.plastic/VERSION" ] && VERSION=$(tr -d ' \t\n' < "$HOME/.plastic/VERSION")
46
+
47
+ # --- Update (next version when one is available) ---
48
+ NEXT=""
49
+ CACHE_FILE="$HOME/.plastic/.cache/update-check.json"
50
+ if [ -f "$CACHE_FILE" ]; then
51
+ if grep -q '"updateAvailable"[[:space:]]*:[[:space:]]*true' "$CACHE_FILE" 2>/dev/null; then
52
+ LATEST=$(grep -o '"latest"[[:space:]]*:[[:space:]]*"[^"]*"' "$CACHE_FILE" 2>/dev/null \
53
+ | head -1 | sed 's/.*"latest"[[:space:]]*:[[:space:]]*"//;s/"$//')
54
+ if [ -n "$LATEST" ]; then
55
+ case "$LATEST" in
56
+ *-*) NEXT="${LATEST##*-}" ;; # 1.0.0-beta.3 -> beta.3
57
+ *) NEXT="$LATEST" ;;
58
+ esac
59
+ fi
17
60
  fi
18
61
  fi
19
62
 
20
- # --- Plastic status ---
21
- PLASTIC_PARTS=""
63
+ # --- Scope resolution: cwd -> registered project store, else global ---
64
+ # Match the longest registered project path that prefixes the real cwd.
65
+ PROJECTS_YML="$HOME/.plastic/projects.yml"
66
+ SLUG=""
67
+ if [ -n "$REAL_CWD" ] && [ -f "$PROJECTS_YML" ]; then
68
+ SLUG=$(awk -v cwd="$REAL_CWD" '
69
+ /^ [A-Za-z0-9_.-]+:[[:space:]]*$/ { s=$1; sub(/:$/,"",s); next }
70
+ /^ path:/ {
71
+ p=$0; sub(/^[[:space:]]*path:[[:space:]]*/,"",p); gsub(/"/,"",p);
72
+ if (length(p)>0 && (cwd==p || index(cwd, p"/")==1) && length(p)>best) { best=length(p); win=s }
73
+ }
74
+ END { if (win!="") print win }
75
+ ' "$PROJECTS_YML")
76
+ fi
22
77
 
23
- # Active intent from INDEX.md
24
- INDEX="$HOME/.plastic/INDEX.md"
25
- if [ -f "$INDEX" ]; then
26
- ACTIVE=$(sed -n '/^## Active$/,/^## /{/^- \[/p;}' "$INDEX" | head -1)
27
- if [ -n "$ACTIVE" ]; then
28
- INTENT=$(echo "$ACTIVE" | sed 's/^- \[\([^]]*\)\].*/\1/')
29
- PLASTIC_PARTS="plastic: ${INTENT}"
30
- fi
78
+ if [ -n "$SLUG" ]; then
79
+ INDEX="$HOME/.plastic/projects/$SLUG/INDEX.md"
80
+ PROJ_LABEL="$SLUG"
81
+ else
82
+ INDEX="$HOME/.plastic/INDEX.md"
83
+ PROJ_LABEL="global"
31
84
  fi
85
+ INDEX_DIR=$(dirname "$INDEX")
32
86
 
33
- # Update notification
34
- CACHE_FILE="$HOME/.plastic/.cache/update-check.json"
35
- if [ -f "$CACHE_FILE" ]; then
36
- UPDATE_AVAILABLE=$(grep -o '"updateAvailable":true' "$CACHE_FILE" 2>/dev/null)
37
- if [ -n "$UPDATE_AVAILABLE" ]; then
38
- LATEST=$(grep -o '"latest":"[^"]*"' "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f4)
39
- CURRENT=$(grep -o '"current":"[^"]*"' "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f4)
40
- UPDATE_MSG="plastic update: $CURRENT -> $LATEST"
41
- if [ -n "$PLASTIC_PARTS" ]; then
42
- PLASTIC_PARTS="$PLASTIC_PARTS | $UPDATE_MSG"
43
- else
44
- PLASTIC_PARTS="$UPDATE_MSG"
87
+ # --- Current-session work-unit: the live bridge wins over savepoint recency ---
88
+ # The statusline receives the real session_id on stdin; the session's bridge
89
+ # (plastic-{session_id}.json) is the authoritative "what THIS session is driving".
90
+ # This beats the shared, savepoint-recency heuristic so parallel sessions on one
91
+ # store no longer overwrite each other's line. grep/sed only - no jq, no ruby.
92
+ WORK=""
93
+ SID=$(json_str "session_id")
94
+ if [ -n "$SID" ]; then
95
+ BRIDGE="${PLASTIC_TMP:-/tmp}/plastic-${SID}.json"
96
+ if [ -f "$BRIDGE" ]; then
97
+ # Bridge JSON is pretty-printed (one field per line). Scope extraction to the
98
+ # "intent" object so a sibling top-level "id"/"name" can never win (the intent
99
+ # object holds only string fields, so the first "}" closes it). grep/sed only.
100
+ INTENT_BLOCK=$(sed -n '/"intent"[[:space:]]*:[[:space:]]*{/,/}/p' "$BRIDGE" 2>/dev/null)
101
+ B_ID=$(printf '%s\n' "$INTENT_BLOCK" | grep -o '"id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 \
102
+ | sed 's/.*"id"[[:space:]]*:[[:space:]]*"//;s/"$//')
103
+ B_NAME=$(printf '%s\n' "$INTENT_BLOCK" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 \
104
+ | sed 's/.*"name"[[:space:]]*:[[:space:]]*"//;s/"$//')
105
+ if [ -n "$B_ID" ] && [ -n "$B_NAME" ]; then
106
+ EMDASH=$'\342\200\224'
107
+ WORK="${B_ID} ${EMDASH} ${B_NAME}"
45
108
  fi
46
109
  fi
47
110
  fi
48
111
 
49
- # --- Combine output ---
50
- if [ -n "$ORIGINAL_OUTPUT" ] && [ -n "$PLASTIC_PARTS" ]; then
51
- echo "$ORIGINAL_OUTPUT | $PLASTIC_PARTS"
52
- elif [ -n "$ORIGINAL_OUTPUT" ]; then
53
- echo "$ORIGINAL_OUTPUT"
54
- elif [ -n "$PLASTIC_PARTS" ]; then
55
- echo "$PLASTIC_PARTS"
112
+ # --- Most-recently-accessed active intent (mirror dashboard: last savepoint ts) ---
113
+ # Fallback only: runs when no session bridge resolved a work-unit above.
114
+ if [ -z "$WORK" ] && [ -f "$INDEX" ]; then
115
+ BEST_TS=""
116
+ while IFS= read -r line; do
117
+ [ -z "$line" ] && continue
118
+ inside=${line#*[}; inside=${inside%%]*} # "{id} - {title}"
119
+ dir=${line#*](store/}; dir=${dir%%/*} # intent directory name
120
+ [ -z "$dir" ] && continue
121
+ sp="$INDEX_DIR/store/$dir/savepoint.md"
122
+ ts=""
123
+ if [ -f "$sp" ]; then
124
+ ts=$(grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z' "$sp" 2>/dev/null | tail -1)
125
+ fi
126
+ # ISO8601 sorts lexicographically; keep the newest. Empty ts sorts lowest.
127
+ if [ -z "$BEST_TS" ] || { [ -n "$ts" ] && [ "$ts" \> "$BEST_TS" ]; }; then
128
+ BEST_TS="$ts"
129
+ WORK="$inside"
130
+ fi
131
+ done <<EOF
132
+ $(sed -n '/^## Active$/,/^## /{/^- \[/p;}' "$INDEX")
133
+ EOF
134
+ fi
135
+
136
+ # --- Build the work-unit "[{project}: {id} {title}]" with title truncation ---
137
+ WORK_SEG=""
138
+ if [ -n "$WORK" ]; then
139
+ # Split "{id} - {title}" on the em-dash (U+2014).
140
+ EMDASH=$'\342\200\224'
141
+ id=${WORK%% $EMDASH *}
142
+ title=${WORK#* $EMDASH }
143
+ # Fallback if the em-dash form is absent: split on first space.
144
+ if [ "$id" = "$WORK" ]; then id=${WORK%% *}; title=${WORK#* }; fi
145
+ # Truncate long titles (~40 chars).
146
+ if [ ${#title} -gt 40 ]; then title="$(printf '%.40s' "$title")..."; fi
147
+ WORK_SEG="${C_BRACK}[${RST}${C_PROJ}${PROJ_LABEL}${RST}${C_INTENT}: ${RST}${C_ID}${id}${RST}${C_INTENT} ${title}${RST}${C_BRACK}]${RST}"
56
148
  fi
149
+
150
+ # --- Assemble segments in order, joined by " . " ---
151
+ PARTS=""
152
+ add() { [ -z "$1" ] && return; if [ -z "$PARTS" ]; then PARTS="$1"; else PARTS="$PARTS$SEP$1"; fi; }
153
+
154
+ # Order: stable values first (model, work-unit), then the volatile-length
155
+ # values last (version, path) so the line does not visually jump as they change.
156
+ [ -n "$MODEL" ] && add "${C_MODEL}${MODEL}${RST}"
157
+ [ -n "$WORK_SEG" ] && add "$WORK_SEG"
158
+ if [ -n "$VERSION" ]; then
159
+ vseg="${C_VER}Plastic ${VERSION}${RST}"
160
+ [ -n "$NEXT" ] && vseg="${vseg}${C_VER} "$'\342\206\222'" ${RST}${C_NEXT}${NEXT}${RST}"
161
+ add "$vseg"
162
+ fi
163
+ [ -n "$CWD" ] && add "${C_PATH}${CWD}${RST}"
164
+
165
+ printf '%s\n' "$PARTS"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.31",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -108,6 +108,26 @@ def outcome_line(intent_dir)
108
108
  "(no outcome yet)"
109
109
  end
110
110
 
111
+ # The insights line: the LAST line of the intent file's `## Insights` section
112
+ # (the newest nugget, since entries are append-only newest-at-bottom), else
113
+ # `(none)`. Pure read of the intent dir, no clock.
114
+ def insights_line(intent_dir)
115
+ ifile = Bridge.intent_file(intent_dir)
116
+ return "(none)" unless File.exist?(ifile)
117
+
118
+ lines = File.read(ifile).split("\n", -1)
119
+ idx = lines.index { |l| l.strip == "## Insights" }
120
+ return "(none)" if idx.nil?
121
+
122
+ last = nil
123
+ (idx + 1).upto(lines.length - 1) do |i|
124
+ break if lines[i].start_with?("## ")
125
+
126
+ last = lines[i] unless lines[i].strip.empty?
127
+ end
128
+ last || "(none)"
129
+ end
130
+
111
131
  intent_dir_arg, role = parse_args(ARGV)
112
132
 
113
133
  if intent_dir_arg.nil? || intent_dir_arg.empty?
@@ -137,6 +157,7 @@ lines << "Status: synthesized (filesystem-derived; no agent-authored report)"
137
157
  lines << "Artifacts present: #{artifacts_str}"
138
158
  lines << "Checklist: #{checklist_progress(intent_dir)}"
139
159
  lines << "Outcome: #{outcome_line(intent_dir)}"
160
+ lines << "Insights: #{insights_line(intent_dir)}"
140
161
  lines << "=== end report ==="
141
162
 
142
163
  puts lines.join("\n")