@peter.naydenov/morph 3.1.5 → 3.2.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.
Files changed (41) hide show
  1. package/.mocharc.json +1 -0
  2. package/.opencode/command/speckit.analyze.md +184 -0
  3. package/.opencode/command/speckit.checklist.md +294 -0
  4. package/.opencode/command/speckit.clarify.md +181 -0
  5. package/.opencode/command/speckit.constitution.md +82 -0
  6. package/.opencode/command/speckit.implement.md +135 -0
  7. package/.opencode/command/speckit.plan.md +89 -0
  8. package/.opencode/command/speckit.specify.md +257 -0
  9. package/.opencode/command/speckit.tasks.md +137 -0
  10. package/.opencode/command/speckit.taskstoissues.md +28 -0
  11. package/.specify/memory/constitution.md +43 -0
  12. package/.specify/scripts/bash/check-prerequisites.sh +166 -0
  13. package/.specify/scripts/bash/common.sh +156 -0
  14. package/.specify/scripts/bash/create-new-feature.sh +305 -0
  15. package/.specify/scripts/bash/setup-plan.sh +61 -0
  16. package/.specify/scripts/bash/update-agent-context.sh +790 -0
  17. package/.specify/templates/agent-file-template.md +28 -0
  18. package/.specify/templates/checklist-template.md +40 -0
  19. package/.specify/templates/plan-template.md +104 -0
  20. package/.specify/templates/spec-template.md +115 -0
  21. package/.specify/templates/tasks-template.md +251 -0
  22. package/Changelog.md +12 -0
  23. package/README.md +154 -3
  24. package/dist/morph.cjs +1 -1
  25. package/dist/morph.esm.mjs +1 -1
  26. package/dist/morph.umd.js +1 -1
  27. package/morph.png +0 -0
  28. package/package.json +3 -2
  29. package/simple.js +17 -0
  30. package/specs/001-extend-templates/checklists/requirements.md +35 -0
  31. package/specs/001-extend-templates/contracts/set-method.json +39 -0
  32. package/specs/001-extend-templates/data-model.md +76 -0
  33. package/specs/001-extend-templates/plan.md +90 -0
  34. package/specs/001-extend-templates/quickstart.md +56 -0
  35. package/specs/001-extend-templates/research.md +18 -0
  36. package/specs/001-extend-templates/spec.md +139 -0
  37. package/specs/001-extend-templates/tasks.md +255 -0
  38. package/src/methods/_readTemplate.js +22 -46
  39. package/src/methods/_setupActions.js +1 -8
  40. package/src/methods/build.js +98 -43
  41. package/src/methods/render.js +14 -11
@@ -34,8 +34,6 @@ import walk from '@peter.naydenov/walk'
34
34
  */
35
35
  function build ( tpl, extra=false, buildDependencies={} ) {
36
36
  let { hasError, placeholders, chop, helpers, handshake, snippets } = _readTemplate ( tpl );
37
-
38
-
39
37
 
40
38
  if ( hasError ) {
41
39
  function fail () { return hasError }
@@ -44,36 +42,49 @@ function build ( tpl, extra=false, buildDependencies={} ) {
44
42
  else { // If NO Error:
45
43
  const originalPlaceholders = structuredClone ( placeholders );
46
44
  // *** Template recognition complete. Start building the rendering function -->
47
- /**
48
- * Processes template rendering commands with provided data, dependencies, and optional post-processing functions.
49
- *
50
- * @function success
51
- * @typedef { 'render' | 'debug' | 'snippets' } Command
52
- * @param {Command} [command='render'] - The command to execute. Supported commands: 'render', 'debug', 'snippets', or 'snippets:<names>'.
53
- * @param {Object|string} [d={}] - The data object to render, or a string instruction ('raw', 'demo', 'handshake', 'placeholders').
54
- * @param {Object} [dependencies={}] - Additional dependencies to be merged with internal dependencies.
55
- * @param {...any} args - Optional post-processing functions to apply to the rendered output.
56
- * @returns {string|Array[]} The rendered template, snippets, or data depending on the command and input.
57
- *
58
- * @throws {Error} If an unsupported command or instruction is provided.
59
- *
60
- * @example
61
- * // Render a template with data
62
- * success('render', { name: 'Alice' }, { helperFn });
63
- *
64
- * @example
65
- * // Get raw template
66
- * success('debug', 'raw');
67
- *
68
- * @example
69
- * // Render only specific snippets
70
- * success('snippets:header,footer', { ... });
71
- */
72
- function success ( command='render', d={}, dependencies={}, ...args ) {
73
- const cuts = structuredClone ( chop )
74
- let onlySnippets = false;
75
-
76
- if ( ![ 'render', 'debug', 'snippets'].includes ( command ) && !command.startsWith('snippets') ) return `Error: Wrong command "${command}". Available commands: render, debug, snippets.`
45
+ /**
46
+ * Processes template rendering commands with provided data, dependencies, and optional post-processing functions.
47
+ *
48
+ * @function success
49
+ * @typedef { 'render' | 'debug' | 'snippets' | 'curry' | 'set' } Command
50
+ * @param {Command} [command='render'] - The command to execute. Supported commands: 'render', 'debug', 'snippets', 'curry', 'set', or 'snippets:<names>'.
51
+ * @param {Object|string} [d={}] - The data object to render, or a string instruction ('raw', 'demo', 'handshake', 'placeholders', 'count').
52
+ * @param {Object} [dependencies={}] - Additional dependencies to be merged with internal dependencies.
53
+ * @param {...any} args - Optional post-processing functions to apply to the rendered output.
54
+ * @returns {string|Array[]|function} The rendered template, snippets, data, or new function depending on the command and input.
55
+ *
56
+ * @throws {Error} If an unsupported command or instruction is provided.
57
+ *
58
+ * @example
59
+ * // Render a template with data
60
+ * success('render', { name: 'Alice' }, { helperFn });
61
+ *
62
+ * @example
63
+ * // Get raw template
64
+ * success('debug', 'raw');
65
+ *
66
+ * @example
67
+ * // Get count of remaining placeholders
68
+ * success('debug', 'count');
69
+ *
70
+ * @example
71
+ * // Render only specific snippets
72
+ * success('snippets:header,footer', { ... });
73
+ *
74
+ * @example
75
+ * // Curry partial data and get new render function
76
+ * const curried = success('curry', { partial: 'data' });
77
+ * curried('render', { more: 'data' });
78
+ *
79
+ * @example
80
+ * // Set placeholders and get modified template
81
+ * const modified = success('set', { placeholders: { key: 'value' } });
82
+ */
83
+ function success ( command='render', d={}, dependencies={}, ...args ) {
84
+ const cuts = structuredClone ( chop )
85
+ let onlySnippets = false;
86
+ if ( typeof command !== 'string' ) return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
87
+ if ( ![ 'render', 'debug', 'snippets', 'set', 'curry'].includes ( command ) && !command.startsWith('snippets') ) return `Error: Wrong command "${command}". Available commands: render, debug, snippets, set, curry.`
77
88
 
78
89
  if ( command.startsWith ( 'snippets') && command.includes ( ':' ) ) {
79
90
  onlySnippets = true
@@ -87,9 +98,49 @@ function build ( tpl, extra=false, buildDependencies={} ) {
87
98
  else if ( command === 'snippets' ) {
88
99
  onlySnippets = true
89
100
  }
90
- else placeholders = structuredClone ( originalPlaceholders ) // Reset placeholders if not snippets
101
+ else if ( command === 'set' ) {
102
+ if ( typeof d !== 'object' || !d ) return `Error: 'set' command requires an object with placeholders, helpers, handshake.`
103
+ // Merge helpers
104
+ const newHelpers = { ...helpers, ...(d.helpers || {}) }
105
+
106
+ // Merge handshake
107
+ const newHandshake = handshake ? { ...handshake, ...(d.handshake || {}) } : d.handshake || {}
108
+ // Modify chop for placeholders
109
+ const newChop = [...chop]
110
+ if ( d.placeholders ) {
111
+ Object.entries ( d.placeholders ).forEach ( ([k,v]) => {
112
+ if ( !isNaN(k) ) { // If k is a number
113
+ let index = placeholders[k].index;
114
+ newChop[index] = v
115
+ } // if k is a number
116
+ else { // If k is a string
117
+ let plx = placeholders.find ( p => p.name === k )
118
+ newChop[ plx.index ] = v
119
+ }
120
+ }) // placeholders
121
+ } // if d.placeholders
122
+ const newTemplateStr = newChop.join ( '' );
123
+ const newTpl = {
124
+ template: newTemplateStr,
125
+ helpers: newHelpers,
126
+ handshake: newHandshake
127
+ }
128
+ const result = build ( newTpl, false, buildDependencies )
129
+ return typeof result === 'function' ? result : () => result
130
+ } else if ( command === 'curry' ) {
131
+ // Render the template with current data
132
+ const rendered = success('render', d, dependencies, ...args)
133
+ // Create new template with rendered as template, keep helpers and handshake
134
+ const newTpl = {
135
+ template: rendered,
136
+ helpers,
137
+ handshake
138
+ }
139
+ const newFn = build(newTpl, false, buildDependencies)
140
+ return newFn
141
+ } else placeholders = structuredClone ( originalPlaceholders ) // Reset placeholders if not snippets
91
142
 
92
- if ( typeof d === 'string' ) {
143
+ if ( typeof d === 'string' ) {
93
144
  switch ( d ) {
94
145
  case 'raw':
95
146
  return cuts.join ( '' ) // Original template with placeholders
@@ -100,13 +151,17 @@ function build ( tpl, extra=false, buildDependencies={} ) {
100
151
  case 'handshake':
101
152
  if ( !handshake ) return `Error: No handshake data.`
102
153
  return structuredClone (handshake) // return a copy of handshake object
103
- case 'placeholders':
104
- return placeholders.map ( h => cuts[h.index] ).join ( ', ')
105
- default:
106
- return `Error: Wrong instruction "${d}". Available instructions: raw, demo, handshake, placeholders.`
154
+ case 'helpers' :
155
+ return Object.keys ( helpers ).join ( ', ' )
156
+ case 'placeholders':
157
+ return placeholders.map ( h => cuts[h.index] ).join ( ', ')
158
+ case 'count':
159
+ return placeholders.length
160
+ default:
161
+ return `Error: Wrong instruction "${d}". Available instructions: raw, demo, handshake, helpers, placeholders, count.`
107
162
  }
108
163
  } // if d is string
109
-
164
+
110
165
  const endData = [];
111
166
  const memory = {};
112
167
 
@@ -177,10 +232,10 @@ function build ( tpl, extra=false, buildDependencies={} ) {
177
232
 
178
233
  levelData.forEach ( (theData, iData ) => {
179
234
 
180
- let dataType = _defineDataType ( theData )
181
-
235
+ let dataType = _defineDataType ( theData )
236
+
182
237
  switch ( type ) { // Action type 'route','data', 'render', or mix -> different operations
183
- case 'route':
238
+ case 'route': // DEPRICATED in version 3.2.0. This functionality can be replaced with normal render functions
184
239
  switch ( dataType ) {
185
240
  case 'array':
186
241
  theData.forEach ( (d,i) => {
@@ -270,7 +325,7 @@ function build ( tpl, extra=false, buildDependencies={} ) {
270
325
  }
271
326
  break
272
327
  case 'mix':
273
- if ( name === '' ) { // when is anonymous mixing helper
328
+ if ( name === '' ) { // when is anonymous mixing action
274
329
  switch ( dataType ) {
275
330
  case 'object':
276
331
  let kTest = Object.keys ( theData ).find ( k => k.includes ( '/' ) ); // Check if keys are breadcrumbs
@@ -33,21 +33,24 @@ function render ( theData, name, helpers, original, dependencies, ...args ) {
33
33
  if ( value instanceof Array ) theData[key] = value[0]
34
34
  })
35
35
  }
36
+
36
37
  /**
37
- * Normalizes render data by wrapping strings in text objects.
38
- *
39
- * @param {any} d - Data to normalize
40
- * @returns {object} Returns { text: d } if d is string, otherwise returns d unchanged
41
- */
42
- function setRenderData ( d={} ) {
38
+ * Normalizes render data by wrapping strings in text objects.
39
+ *
40
+ * @param {any} d - Data to normalize
41
+ * @returns {object} Returns { text: d } if d is string, otherwise returns d unchanged
42
+ */
43
+ function setRenderData ( d={} ) {
43
44
  if ( typeof d === 'string' ) return { text: d }
44
45
  else return d
45
46
  } // setRenderData func.
46
- const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function or template.
47
- theData = setRenderData ( theData )
48
-
49
- if ( isRenderFunction ) return helpers[name]( { theData, dependencies, full:original}, ...args )
50
- else return _renderHolder ( helpers[name], theData )
47
+
48
+ if (!helpers[name]) return `{{ Error: Helper '${name}' is not available}}`
49
+ const isRenderFunction = typeof helpers[name] === 'function'; // Render could be a function or template.
50
+ theData = setRenderData ( theData )
51
+
52
+ if ( isRenderFunction ) return helpers[name]( { theData, dependencies, full:original}, ...args )
53
+ else return _renderHolder ( helpers[name], theData )
51
54
  } // render func.
52
55
 
53
56